https://github.com/rnk created https://github.com/llvm/llvm-project/pull/219015
This is mostly the result of reviewing #218017 and prompting an agent to go and
do some more ABI edge-case testing with godbolt. It worked! It was able to call
the compiler-explorer HTTP endpoint and capture the output of various GCC and
MSVC versions for ABI analysis.
I was surprised that our logic is mostly already correct. I was surprised that
GCC specifically cares about vector types, and it doesn't seem to care about
overaligned struct types. It also only seems to recurse into structs in the
variadic argument case. Regardless, the old code structure really prompted me
to ask if we were covering all cases correctly, and I hope the new structure is
clearer, even if it takes more code.
Restructure the x86_32 OS ABI dispatch logic into 3 buckets:
- Linux / mingw first: These rules match GCC, and apply to platforms where it
is the dominant system compiler defining the ABI
- MSVC Windows second: Match MSVC there
- SysV, Darwin, BSD, everything else: These platforms prioritize ABI stability
for legacy platforms, so we typically use 4 byte alignment, and 16 for SSE
vectors in some cases, but in general, "no change" is best for legacy
unsupported platforms.
Add getX86VectorRegisterVarArgAlign(), replacing the old "Ty->isVectorType() &&
Align in {16, 32, 64}" check with one that also covers __float128 and, for
variadic arguments only. This helper recurses into struct/class fields
(including array element types) and bases, returning the natural alignment of
the widest vector-register type found. It checks the type's own fixed size
rather than its (possibly attribute-inflated) alignment, and deliberately
ignores a record's own forced over-alignment in favor of its widest member's
natural one. This matches how modern versions of GCC handle alignment, as
confirmed with experiments on godbolt.
getTypeStackAlignInBytes() needs an IsVarArg parameter, since GCC testing
showed that recursing into record types to find vectors only applies to
variadic arguments.
Agent-assisted
>From db27e76b6d60e5fd34eb4f2d78a8046bb15dd581 Mon Sep 17 00:00:00 2001
From: Reid Kleckner <[email protected]>
Date: Tue, 25 Aug 2026 16:54:27 +0000
Subject: [PATCH] [X86] Rewrite X86_32ABIInfo::getTypeStackAlignInBytes for
clarity/correctness
Restructure the x86_32 OS ABI dispatch logic into 3 buckets:
- Linux / mingw first: These rules match GCC, and apply to platforms
where it is the dominant system compiler defining the ABI
- MSVC Windows second: Match MSVC there
- SysV, Darwin, BSD, everything else: These platforms prioritize ABI
stability for legacy platforms, so we typically use 4 byte alignment,
and 16 for SSE vectors in some cases, but in general, "no change" is
best for legacy unsupported platforms.
Add getX86VectorRegisterVarArgAlign(), replacing the old
"Ty->isVectorType() && Align in {16, 32, 64}" check with one that also
covers __float128 and, for variadic arguments only. This helper recurses
into struct/class fields (including array element types) and bases,
returning the natural alignment of the widest vector-register type
found. It checks the type's own fixed size rather than its (possibly
attribute-inflated) alignment, and deliberately ignores a record's own
forced over-alignment in favor of its widest member's natural one. This
matches how modern versions of GCC handle alignment, as confirmed with
experiments on godbolt.
getTypeStackAlignInBytes() gains an IsVarArg parameter, since GCC
testing showed that recursing into record types to find vectors only
applies to variadic arguments.
This is mostly the result of reviewing #218017 and prompting an agent to
go and do some more ABI edge-case testing with godbolt. It worked! It
was able to call the compiler-explorer HTTP endpoint and capture the
output of various GCC and MSVC versions for ABI analysis.
---
clang/lib/CodeGen/Targets/X86.cpp | 96 ++++++-
clang/test/CodeGen/X86/x86_32-vaarg-msvc.c | 34 +++
clang/test/CodeGen/X86/x86_32-vaarg.c | 305 ++++++++++++++++++++-
clang/test/CodeGenCXX/x86_32-vaarg.cpp | 31 ++-
4 files changed, 447 insertions(+), 19 deletions(-)
create mode 100644 clang/test/CodeGen/X86/x86_32-vaarg-msvc.c
diff --git a/clang/lib/CodeGen/Targets/X86.cpp
b/clang/lib/CodeGen/Targets/X86.cpp
index 1dc3bd0740baa..988b8043b7514 100644
--- a/clang/lib/CodeGen/Targets/X86.cpp
+++ b/clang/lib/CodeGen/Targets/X86.cpp
@@ -85,6 +85,56 @@ static ABIArgInfo getDirectX86Hva(llvm::Type* T = nullptr) {
// X86-32 ABI Implementation
//===----------------------------------------------------------------------===//
+/// Returns the natural alignment (16, 32, or 64) of the X86 vector-register
+/// type \p Ty is, or (if CheckRecordFields) recursively contains; else 0.
+/// Matches GCC's varargs realignment rule on Linux/mingw, which applies only
+/// to vector/__float128 types, using their own fixed size rather than any
+/// attribute-inflated alignment (so an over-aligned typedef of a 16-byte
+/// vector still only gets 16-byte realignment, matching GCC).
+static unsigned getX86VectorRegisterVarArgAlign(ASTContext &Context,
+ QualType Ty,
+ bool CheckRecordFields) {
+ if (Ty->isFloat128Type())
+ return 16;
+
+ if (Ty->isVectorType()) {
+ uint64_t Bytes = Context.getTypeSizeInChars(Ty).getQuantity();
+ return (Bytes == 16 || Bytes == 32 || Bytes == 64) ? Bytes : 0;
+ }
+
+ // Only variadic arguments recurse into struct/class fields and bases: GCC
+ // does not extend this realignment to byval parameters of the same
+ // type (see the "misaligns parameters on stack" comment on
+ // CodeGen/X86/x86_32-arguments-linux.c).
+ if (!CheckRecordFields)
+ return 0;
+
+ if (Ty->isArrayType())
+ return getX86VectorRegisterVarArgAlign(
+ Context, Context.getBaseElementType(Ty), CheckRecordFields);
+
+ const auto *RD = Ty->getAsRecordDecl();
+ if (!RD)
+ return 0;
+
+ // Use the widest member's own alignment, not the record's (which may be
+ // further over-aligned by attribute); GCC's handling of that case is
+ // itself version-dependent, so we don't chase it here.
+ unsigned Result = 0;
+ if (const auto *CXXRD = dyn_cast<CXXRecordDecl>(RD))
+ for (const auto &Base : CXXRD->bases())
+ Result =
+ std::max(Result, getX86VectorRegisterVarArgAlign(
+ Context, Base.getType(), CheckRecordFields));
+
+ for (const auto *Field : RD->fields())
+ Result =
+ std::max(Result, getX86VectorRegisterVarArgAlign(
+ Context, Field->getType(), CheckRecordFields));
+
+ return Result;
+}
+
/// Similar to llvm::CCState, but for Clang.
struct CCState {
CCState(CGFunctionInfo &FI)
@@ -139,8 +189,11 @@ class X86_32ABIInfo : public ABIInfo {
ABIArgInfo getIndirectReturnResult(QualType Ty, CCState &State) const;
- /// Return the alignment to use for the given type on the stack.
- unsigned getTypeStackAlignInBytes(QualType Ty, unsigned Align) const;
+ /// Return the alignment to use for the given type on the stack. \p
+ /// IsVarArg distinguishes a "..." (va_arg) argument from an ordinary
+ /// (possibly byval) parameter; see getX86VectorRegisterVarArgAlign().
+ unsigned getTypeStackAlignInBytes(QualType Ty, unsigned Align,
+ bool IsVarArg) const;
Class classify(QualType Ty) const;
ABIArgInfo classifyReturnType(QualType RetTy, CCState &State) const;
@@ -572,24 +625,36 @@ ABIArgInfo X86_32ABIInfo::classifyReturnType(QualType
RetTy,
: ABIArgInfo::getDirect());
}
-unsigned X86_32ABIInfo::getTypeStackAlignInBytes(QualType Ty,
- unsigned Align) const {
+unsigned X86_32ABIInfo::getTypeStackAlignInBytes(QualType Ty, unsigned Align,
+ bool IsVarArg) const {
// Otherwise, if the alignment is less than or equal to the minimum ABI
// alignment, just use the default; the backend will handle this.
if (Align <= MinABIStackAlignInBytes)
return 0; // Use default alignment.
+ // Linux and the GNU-flavored Windows targets (MinGW, Cygwin) are the
+ // platforms where GCC, not Clang, defines this part of the ABI, so match
+ // it: realign "..." arguments to their natural alignment, but only for
+ // vector-register types (see getX86VectorRegisterVarArgAlign()).
+ if (IsLinuxABI) {
+ if (unsigned VecAlign = getX86VectorRegisterVarArgAlign(
+ getContext(), Ty, /*CheckRecordFields=*/IsVarArg))
+ return VecAlign;
+ }
+
+ // Real (non-GNU) Windows never honors over-alignment for "..." arguments,
+ // for any type: MSVC packs them all at the platform default (4 bytes),
+ // unlike a named parameter of the same type, which it passes indirectly
+ // by pointer once its alignment exceeds 4 bytes.
+ if (IsWin32StructABI)
+ return MinABIStackAlignInBytes;
+
+ // FreeBSD/NetBSD/OpenBSD/PS4 and Darwin keep their own narrower,
+ // long-standing behavior below: Clang isn't tracking a reference
+ // compiler's ABI for these, so don't widen what gets realigned here.
if (Ty->isFloat128Type())
return 16;
- if (IsLinuxABI) {
- // Exclude other System V OS (e.g Darwin, PS4 and FreeBSD) since we don't
- // want to spend any effort dealing with the ramifications of ABI breaks.
- //
- // If the vector type is __m128/__m256/__m512, return the default
alignment.
- if (Ty->isVectorType() && (Align == 16 || Align == 32 || Align == 64))
- return Align;
- }
// On non-Darwin, the stack type alignment is always 4.
if (!IsDarwinVectorABI) {
// Set explicit alignment, since we may need to realign the top.
@@ -618,7 +683,8 @@ ABIArgInfo X86_32ABIInfo::getIndirectResult(QualType Ty,
bool ByVal,
// Compute the byval alignment.
unsigned TypeAlign = getContext().getTypeAlign(Ty) / 8;
- unsigned StackAlign = getTypeStackAlignInBytes(Ty, TypeAlign);
+ unsigned StackAlign =
+ getTypeStackAlignInBytes(Ty, TypeAlign, /*IsVarArg=*/false);
if (StackAlign == 0)
return ABIArgInfo::getIndirect(
CharUnits::fromQuantity(4),
@@ -1092,8 +1158,8 @@ RValue X86_32ABIInfo::EmitVAArg(CodeGenFunction &CGF,
Address VAListAddr,
//
// Just messing with TypeInfo like this works because we never pass
// anything indirectly.
- TypeInfo.Align = CharUnits::fromQuantity(
- getTypeStackAlignInBytes(Ty, TypeInfo.Align.getQuantity()));
+ TypeInfo.Align = CharUnits::fromQuantity(getTypeStackAlignInBytes(
+ Ty, TypeInfo.Align.getQuantity(), /*IsVarArg=*/true));
return emitVoidPtrVAArg(CGF, VAListAddr, Ty, /*Indirect*/ false, TypeInfo,
CharUnits::fromQuantity(4),
diff --git a/clang/test/CodeGen/X86/x86_32-vaarg-msvc.c
b/clang/test/CodeGen/X86/x86_32-vaarg-msvc.c
new file mode 100644
index 0000000000000..e7dfafc78d716
--- /dev/null
+++ b/clang/test/CodeGen/X86/x86_32-vaarg-msvc.c
@@ -0,0 +1,34 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py
UTC_ARGS: --version 6
+// RUN: %clang_cc1 -triple i386-pc-windows-msvc -fms-compatibility -emit-llvm
-o - %s | FileCheck %s
+
+// Unlike the GNU/SysV-flavored x86-32 targets (see x86_32-vaarg.c), the real
+// (non-GNU) Windows ABI never realigns "..." arguments to their natural
+// alignment: va_arg reads a vector argument with an unaligned (align 4)
+// load, matching MSVC, which packs variadic arguments at the platform
+// default alignment instead of honoring over-alignment through varargs.
+typedef float __v4sf __attribute__((__vector_size__(16)));
+
+// CHECK-LABEL: define dso_local <4 x float> @vec_test(
+// CHECK-SAME: i32 noundef [[Z:%.*]], ...) #[[ATTR0:[0-9]+]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[Z_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[LIST:%.*]] = alloca ptr, align 4
+// CHECK-NEXT: [[X:%.*]] = alloca <4 x float>, align 16
+// CHECK-NEXT: store i32 [[Z]], ptr [[Z_ADDR]], align 4
+// CHECK-NEXT: call void @llvm.va_start.p0(ptr [[LIST]])
+// CHECK-NEXT: [[ARGP_CUR:%.*]] = load ptr, ptr [[LIST]], align 4
+// CHECK-NEXT: [[ARGP_NEXT:%.*]] = getelementptr inbounds i8, ptr
[[ARGP_CUR]], i32 16
+// CHECK-NEXT: store ptr [[ARGP_NEXT]], ptr [[LIST]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load <4 x float>, ptr [[ARGP_CUR]], align 4
+// CHECK-NEXT: store <4 x float> [[TMP0]], ptr [[X]], align 16
+// CHECK-NEXT: call void @llvm.va_end.p0(ptr [[LIST]])
+// CHECK-NEXT: [[TMP1:%.*]] = load <4 x float>, ptr [[X]], align 16
+// CHECK-NEXT: ret <4 x float> [[TMP1]]
+//
+__v4sf vec_test(int z, ...) {
+ __builtin_va_list list;
+ __builtin_va_start(list, z);
+ __v4sf x = __builtin_va_arg(list, __v4sf);
+ __builtin_va_end(list);
+ return x;
+}
diff --git a/clang/test/CodeGen/X86/x86_32-vaarg.c
b/clang/test/CodeGen/X86/x86_32-vaarg.c
index 3fa90c0420cad..7aac1bacacb09 100644
--- a/clang/test/CodeGen/X86/x86_32-vaarg.c
+++ b/clang/test/CodeGen/X86/x86_32-vaarg.c
@@ -1,7 +1,7 @@
// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py
UTC_ARGS: --version 6
-// RUN: %clang_cc1 -triple i386-unknown-linux-gnu -emit-llvm -o - %s |
FileCheck %s
-// RUN: %clang_cc1 -triple i386-unknown-freebsd -emit-llvm -o - %s | FileCheck
%s
-// RUN: %clang_cc1 -triple i686-windows-gnu -emit-llvm -o - %s | FileCheck %s
+// RUN: %clang_cc1 -triple i386-unknown-linux-gnu -emit-llvm -o - %s |
FileCheck %s --check-prefixes=CHECK,LINUXABI
+// RUN: %clang_cc1 -triple i386-unknown-freebsd -emit-llvm -o - %s | FileCheck
%s --check-prefixes=CHECK,SYSV
+// RUN: %clang_cc1 -triple i686-windows-gnu -emit-llvm -o - %s | FileCheck %s
--check-prefixes=CHECK,LINUXABI
// CHECK-LABEL: define dso_local fp128 @f128_test(
// CHECK-SAME: i32 noundef [[Z:%.*]], ...) #[[ATTR0:[0-9]+]] {
@@ -104,3 +104,302 @@ int int_test(int z, ...) {
__builtin_va_end(list);
return x;
}
+
+// A struct over-aligned only via an explicit attribute, with no vector
+// register type involved, is NOT realigned on any of these platforms:
+// GCC doesn't realign it either (contrast vector_record_test below).
+typedef struct __attribute__((aligned(16))) { long a, b; } AlignedStruct;
+
+// CHECK-LABEL: define dso_local void @aligned_struct_test(
+// CHECK-SAME: ptr dead_on_unwind noalias writable
sret([[STRUCT_ALIGNEDSTRUCT:%.*]]) align 16 [[AGG_RESULT:%.*]], i32 noundef
[[Z:%.*]], ...) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[RESULT_PTR:%.*]] = alloca ptr, align 4
+// CHECK-NEXT: [[Z_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[LIST:%.*]] = alloca ptr, align 4
+// CHECK-NEXT: store ptr [[AGG_RESULT]], ptr [[RESULT_PTR]], align 4
+// CHECK-NEXT: store i32 [[Z]], ptr [[Z_ADDR]], align 4
+// CHECK-NEXT: call void @llvm.va_start.p0(ptr [[LIST]])
+// CHECK-NEXT: [[ARGP_CUR:%.*]] = load ptr, ptr [[LIST]], align 4
+// CHECK-NEXT: [[ARGP_NEXT:%.*]] = getelementptr inbounds i8, ptr
[[ARGP_CUR]], i32 16
+// CHECK-NEXT: store ptr [[ARGP_NEXT]], ptr [[LIST]], align 4
+// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 16
[[AGG_RESULT]], ptr align 4 [[ARGP_CUR]], i32 16, i1 false)
+// CHECK-NEXT: call void @llvm.va_end.p0(ptr [[LIST]])
+// CHECK-NEXT: ret void
+//
+AlignedStruct aligned_struct_test(int z, ...) {
+ __builtin_va_list list;
+ __builtin_va_start(list, z);
+ AlignedStruct x = __builtin_va_arg(list, AlignedStruct);
+ __builtin_va_end(list);
+ return x;
+}
+
+// A struct containing a vector member IS realigned on Linux/MinGW/Cygwin,
+// matching real GCC, but not on other System V platforms such as FreeBSD,
+// where Clang doesn't track a reference compiler for this part of the ABI.
+typedef float __v4sf __attribute__((__vector_size__(16)));
+typedef struct { int tag; __v4sf v; } VectorRecord;
+
+// LINUXABI-LABEL: define dso_local void @vector_record_test(
+// LINUXABI-SAME: ptr dead_on_unwind noalias writable
sret([[STRUCT_VECTORRECORD:%.*]]) align 16 [[AGG_RESULT:%.*]], i32 noundef
[[Z:%.*]], ...) #[[ATTR0]] {
+// LINUXABI-NEXT: [[ENTRY:.*:]]
+// LINUXABI-NEXT: [[RESULT_PTR:%.*]] = alloca ptr, align 4
+// LINUXABI-NEXT: [[Z_ADDR:%.*]] = alloca i32, align 4
+// LINUXABI-NEXT: [[LIST:%.*]] = alloca ptr, align 4
+// LINUXABI-NEXT: store ptr [[AGG_RESULT]], ptr [[RESULT_PTR]], align 4
+// LINUXABI-NEXT: store i32 [[Z]], ptr [[Z_ADDR]], align 4
+// LINUXABI-NEXT: call void @llvm.va_start.p0(ptr [[LIST]])
+// LINUXABI-NEXT: [[ARGP_CUR:%.*]] = load ptr, ptr [[LIST]], align 4
+// LINUXABI-NEXT: [[TMP0:%.*]] = getelementptr inbounds i8, ptr
[[ARGP_CUR]], i32 15
+// LINUXABI-NEXT: [[ARGP_CUR_ALIGNED:%.*]] = call ptr
@llvm.ptrmask.p0.i32(ptr [[TMP0]], i32 -16)
+// LINUXABI-NEXT: [[ARGP_NEXT:%.*]] = getelementptr inbounds i8, ptr
[[ARGP_CUR_ALIGNED]], i32 32
+// LINUXABI-NEXT: store ptr [[ARGP_NEXT]], ptr [[LIST]], align 4
+// LINUXABI-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 16
[[AGG_RESULT]], ptr align 16 [[ARGP_CUR_ALIGNED]], i32 32, i1 false)
+// LINUXABI-NEXT: call void @llvm.va_end.p0(ptr [[LIST]])
+// LINUXABI-NEXT: ret void
+//
+// SYSV-LABEL: define dso_local void @vector_record_test(
+// SYSV-SAME: ptr dead_on_unwind noalias writable
sret([[STRUCT_VECTORRECORD:%.*]]) align 16 [[AGG_RESULT:%.*]], i32 noundef
[[Z:%.*]], ...) #[[ATTR0]] {
+// SYSV-NEXT: [[ENTRY:.*:]]
+// SYSV-NEXT: [[RESULT_PTR:%.*]] = alloca ptr, align 4
+// SYSV-NEXT: [[Z_ADDR:%.*]] = alloca i32, align 4
+// SYSV-NEXT: [[LIST:%.*]] = alloca ptr, align 4
+// SYSV-NEXT: store ptr [[AGG_RESULT]], ptr [[RESULT_PTR]], align 4
+// SYSV-NEXT: store i32 [[Z]], ptr [[Z_ADDR]], align 4
+// SYSV-NEXT: call void @llvm.va_start.p0(ptr [[LIST]])
+// SYSV-NEXT: [[ARGP_CUR:%.*]] = load ptr, ptr [[LIST]], align 4
+// SYSV-NEXT: [[ARGP_NEXT:%.*]] = getelementptr inbounds i8, ptr
[[ARGP_CUR]], i32 32
+// SYSV-NEXT: store ptr [[ARGP_NEXT]], ptr [[LIST]], align 4
+// SYSV-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 16 [[AGG_RESULT]],
ptr align 4 [[ARGP_CUR]], i32 32, i1 false)
+// SYSV-NEXT: call void @llvm.va_end.p0(ptr [[LIST]])
+// SYSV-NEXT: ret void
+//
+VectorRecord vector_record_test(int z, ...) {
+ __builtin_va_list list;
+ __builtin_va_start(list, z);
+ VectorRecord x = __builtin_va_arg(list, VectorRecord);
+ __builtin_va_end(list);
+ return x;
+}
+
+// A vector further over-aligned via a typedef attribute is realigned to
+// its own natural (16-byte) alignment, NOT the attribute-forced 32: GCC
+// ignores the extra attribute alignment here too (see the ptrmask below).
+typedef __v4sf OveralignedVector __attribute__((aligned(32)));
+
+// LINUXABI-LABEL: define dso_local <4 x float> @overaligned_vector_test(
+// LINUXABI-SAME: i32 noundef [[Z:%.*]], ...) #[[ATTR4:[0-9]+]] {
+// LINUXABI-NEXT: [[ENTRY:.*:]]
+// LINUXABI-NEXT: [[Z_ADDR:%.*]] = alloca i32, align 4
+// LINUXABI-NEXT: [[LIST:%.*]] = alloca ptr, align 4
+// LINUXABI-NEXT: [[X:%.*]] = alloca <4 x float>, align 32
+// LINUXABI-NEXT: store i32 [[Z]], ptr [[Z_ADDR]], align 4
+// LINUXABI-NEXT: call void @llvm.va_start.p0(ptr [[LIST]])
+// LINUXABI-NEXT: [[ARGP_CUR:%.*]] = load ptr, ptr [[LIST]], align 4
+// LINUXABI-NEXT: [[TMP0:%.*]] = getelementptr inbounds i8, ptr
[[ARGP_CUR]], i32 15
+// LINUXABI-NEXT: [[ARGP_CUR_ALIGNED:%.*]] = call ptr
@llvm.ptrmask.p0.i32(ptr [[TMP0]], i32 -16)
+// LINUXABI-NEXT: [[ARGP_NEXT:%.*]] = getelementptr inbounds i8, ptr
[[ARGP_CUR_ALIGNED]], i32 16
+// LINUXABI-NEXT: store ptr [[ARGP_NEXT]], ptr [[LIST]], align 4
+// LINUXABI-NEXT: [[TMP1:%.*]] = load <4 x float>, ptr
[[ARGP_CUR_ALIGNED]], align 16
+// LINUXABI-NEXT: store <4 x float> [[TMP1]], ptr [[X]], align 32
+// LINUXABI-NEXT: call void @llvm.va_end.p0(ptr [[LIST]])
+// LINUXABI-NEXT: [[TMP2:%.*]] = load <4 x float>, ptr [[X]], align 32
+// LINUXABI-NEXT: ret <4 x float> [[TMP2]]
+//
+// SYSV-LABEL: define dso_local <4 x float> @overaligned_vector_test(
+// SYSV-SAME: i32 noundef [[Z:%.*]], ...) #[[ATTR4:[0-9]+]] {
+// SYSV-NEXT: [[ENTRY:.*:]]
+// SYSV-NEXT: [[Z_ADDR:%.*]] = alloca i32, align 4
+// SYSV-NEXT: [[LIST:%.*]] = alloca ptr, align 4
+// SYSV-NEXT: [[X:%.*]] = alloca <4 x float>, align 32
+// SYSV-NEXT: store i32 [[Z]], ptr [[Z_ADDR]], align 4
+// SYSV-NEXT: call void @llvm.va_start.p0(ptr [[LIST]])
+// SYSV-NEXT: [[ARGP_CUR:%.*]] = load ptr, ptr [[LIST]], align 4
+// SYSV-NEXT: [[ARGP_NEXT:%.*]] = getelementptr inbounds i8, ptr
[[ARGP_CUR]], i32 16
+// SYSV-NEXT: store ptr [[ARGP_NEXT]], ptr [[LIST]], align 4
+// SYSV-NEXT: [[TMP0:%.*]] = load <4 x float>, ptr [[ARGP_CUR]], align 4
+// SYSV-NEXT: store <4 x float> [[TMP0]], ptr [[X]], align 32
+// SYSV-NEXT: call void @llvm.va_end.p0(ptr [[LIST]])
+// SYSV-NEXT: [[TMP1:%.*]] = load <4 x float>, ptr [[X]], align 32
+// SYSV-NEXT: ret <4 x float> [[TMP1]]
+//
+OveralignedVector overaligned_vector_test(int z, ...) {
+ __builtin_va_list list;
+ __builtin_va_start(list, z);
+ OveralignedVector x = __builtin_va_arg(list, OveralignedVector);
+ __builtin_va_end(list);
+ return x;
+}
+
+// A struct containing an array of vector-register-type elements IS
+// realigned on Linux/MinGW/Cygwin, same as a struct containing a bare
+// vector member: the recursive field scan looks through array element
+// types too, matching real GCC.
+typedef struct { __v4sf arr[2]; } VectorArrayRecord;
+
+// LINUXABI-LABEL: define dso_local void @vector_array_record_test(
+// LINUXABI-SAME: ptr dead_on_unwind noalias writable
sret([[STRUCT_VECTORARRAYRECORD:%.*]]) align 16 [[AGG_RESULT:%.*]], i32 noundef
[[Z:%.*]], ...) #[[ATTR0]] {
+// LINUXABI-NEXT: [[ENTRY:.*:]]
+// LINUXABI-NEXT: [[RESULT_PTR:%.*]] = alloca ptr, align 4
+// LINUXABI-NEXT: [[Z_ADDR:%.*]] = alloca i32, align 4
+// LINUXABI-NEXT: [[LIST:%.*]] = alloca ptr, align 4
+// LINUXABI-NEXT: store ptr [[AGG_RESULT]], ptr [[RESULT_PTR]], align 4
+// LINUXABI-NEXT: store i32 [[Z]], ptr [[Z_ADDR]], align 4
+// LINUXABI-NEXT: call void @llvm.va_start.p0(ptr [[LIST]])
+// LINUXABI-NEXT: [[ARGP_CUR:%.*]] = load ptr, ptr [[LIST]], align 4
+// LINUXABI-NEXT: [[TMP0:%.*]] = getelementptr inbounds i8, ptr
[[ARGP_CUR]], i32 15
+// LINUXABI-NEXT: [[ARGP_CUR_ALIGNED:%.*]] = call ptr
@llvm.ptrmask.p0.i32(ptr [[TMP0]], i32 -16)
+// LINUXABI-NEXT: [[ARGP_NEXT:%.*]] = getelementptr inbounds i8, ptr
[[ARGP_CUR_ALIGNED]], i32 32
+// LINUXABI-NEXT: store ptr [[ARGP_NEXT]], ptr [[LIST]], align 4
+// LINUXABI-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 16
[[AGG_RESULT]], ptr align 16 [[ARGP_CUR_ALIGNED]], i32 32, i1 false)
+// LINUXABI-NEXT: call void @llvm.va_end.p0(ptr [[LIST]])
+// LINUXABI-NEXT: ret void
+//
+// SYSV-LABEL: define dso_local void @vector_array_record_test(
+// SYSV-SAME: ptr dead_on_unwind noalias writable
sret([[STRUCT_VECTORARRAYRECORD:%.*]]) align 16 [[AGG_RESULT:%.*]], i32 noundef
[[Z:%.*]], ...) #[[ATTR0]] {
+// SYSV-NEXT: [[ENTRY:.*:]]
+// SYSV-NEXT: [[RESULT_PTR:%.*]] = alloca ptr, align 4
+// SYSV-NEXT: [[Z_ADDR:%.*]] = alloca i32, align 4
+// SYSV-NEXT: [[LIST:%.*]] = alloca ptr, align 4
+// SYSV-NEXT: store ptr [[AGG_RESULT]], ptr [[RESULT_PTR]], align 4
+// SYSV-NEXT: store i32 [[Z]], ptr [[Z_ADDR]], align 4
+// SYSV-NEXT: call void @llvm.va_start.p0(ptr [[LIST]])
+// SYSV-NEXT: [[ARGP_CUR:%.*]] = load ptr, ptr [[LIST]], align 4
+// SYSV-NEXT: [[ARGP_NEXT:%.*]] = getelementptr inbounds i8, ptr
[[ARGP_CUR]], i32 32
+// SYSV-NEXT: store ptr [[ARGP_NEXT]], ptr [[LIST]], align 4
+// SYSV-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 16 [[AGG_RESULT]],
ptr align 4 [[ARGP_CUR]], i32 32, i1 false)
+// SYSV-NEXT: call void @llvm.va_end.p0(ptr [[LIST]])
+// SYSV-NEXT: ret void
+//
+VectorArrayRecord vector_array_record_test(int z, ...) {
+ __builtin_va_list list;
+ __builtin_va_start(list, z);
+ VectorArrayRecord x = __builtin_va_arg(list, VectorArrayRecord);
+ __builtin_va_end(list);
+ return x;
+}
+
+// A 256-bit (AVX) vector is realigned to 32, not just the 128-bit (SSE)
+// case: the check is on the type's own size, not a fixed 16.
+typedef float __v8sf __attribute__((__vector_size__(32)));
+
+// LINUXABI-LABEL: define dso_local <8 x float> @avx_vector_test(
+// LINUXABI-SAME: i32 noundef [[Z:%.*]], ...) #[[ATTR5:[0-9]+]] {
+// LINUXABI-NEXT: [[ENTRY:.*:]]
+// LINUXABI-NEXT: [[Z_ADDR:%.*]] = alloca i32, align 4
+// LINUXABI-NEXT: [[LIST:%.*]] = alloca ptr, align 4
+// LINUXABI-NEXT: [[X:%.*]] = alloca <8 x float>, align 32
+// LINUXABI-NEXT: store i32 [[Z]], ptr [[Z_ADDR]], align 4
+// LINUXABI-NEXT: call void @llvm.va_start.p0(ptr [[LIST]])
+// LINUXABI-NEXT: [[ARGP_CUR:%.*]] = load ptr, ptr [[LIST]], align 4
+// LINUXABI-NEXT: [[TMP0:%.*]] = getelementptr inbounds i8, ptr
[[ARGP_CUR]], i32 31
+// LINUXABI-NEXT: [[ARGP_CUR_ALIGNED:%.*]] = call ptr
@llvm.ptrmask.p0.i32(ptr [[TMP0]], i32 -32)
+// LINUXABI-NEXT: [[ARGP_NEXT:%.*]] = getelementptr inbounds i8, ptr
[[ARGP_CUR_ALIGNED]], i32 32
+// LINUXABI-NEXT: store ptr [[ARGP_NEXT]], ptr [[LIST]], align 4
+// LINUXABI-NEXT: [[TMP1:%.*]] = load <8 x float>, ptr
[[ARGP_CUR_ALIGNED]], align 32
+// LINUXABI-NEXT: store <8 x float> [[TMP1]], ptr [[X]], align 32
+// LINUXABI-NEXT: call void @llvm.va_end.p0(ptr [[LIST]])
+// LINUXABI-NEXT: [[TMP2:%.*]] = load <8 x float>, ptr [[X]], align 32
+// LINUXABI-NEXT: ret <8 x float> [[TMP2]]
+//
+// SYSV-LABEL: define dso_local <8 x float> @avx_vector_test(
+// SYSV-SAME: i32 noundef [[Z:%.*]], ...) #[[ATTR5:[0-9]+]] {
+// SYSV-NEXT: [[ENTRY:.*:]]
+// SYSV-NEXT: [[Z_ADDR:%.*]] = alloca i32, align 4
+// SYSV-NEXT: [[LIST:%.*]] = alloca ptr, align 4
+// SYSV-NEXT: [[X:%.*]] = alloca <8 x float>, align 32
+// SYSV-NEXT: store i32 [[Z]], ptr [[Z_ADDR]], align 4
+// SYSV-NEXT: call void @llvm.va_start.p0(ptr [[LIST]])
+// SYSV-NEXT: [[ARGP_CUR:%.*]] = load ptr, ptr [[LIST]], align 4
+// SYSV-NEXT: [[ARGP_NEXT:%.*]] = getelementptr inbounds i8, ptr
[[ARGP_CUR]], i32 32
+// SYSV-NEXT: store ptr [[ARGP_NEXT]], ptr [[LIST]], align 4
+// SYSV-NEXT: [[TMP0:%.*]] = load <8 x float>, ptr [[ARGP_CUR]], align 4
+// SYSV-NEXT: store <8 x float> [[TMP0]], ptr [[X]], align 32
+// SYSV-NEXT: call void @llvm.va_end.p0(ptr [[LIST]])
+// SYSV-NEXT: [[TMP1:%.*]] = load <8 x float>, ptr [[X]], align 32
+// SYSV-NEXT: ret <8 x float> [[TMP1]]
+//
+__v8sf avx_vector_test(int z, ...) {
+ __builtin_va_list list;
+ __builtin_va_start(list, z);
+ __v8sf x = __builtin_va_arg(list, __v8sf);
+ __builtin_va_end(list);
+ return x;
+}
+
+// An 8-byte (MMX-width) vector is NOT a recognized SSE/AVX/AVX-512
+// register width, so it is NOT realigned, same as everywhere else.
+typedef float __v2sf __attribute__((__vector_size__(8)));
+
+// CHECK-LABEL: define dso_local <2 x float> @mmx_vector_test(
+// CHECK-SAME: i32 noundef [[Z:%.*]], ...) #[[ATTR6:[0-9]+]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[Z_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[LIST:%.*]] = alloca ptr, align 4
+// CHECK-NEXT: [[X:%.*]] = alloca <2 x float>, align 8
+// CHECK-NEXT: store i32 [[Z]], ptr [[Z_ADDR]], align 4
+// CHECK-NEXT: call void @llvm.va_start.p0(ptr [[LIST]])
+// CHECK-NEXT: [[ARGP_CUR:%.*]] = load ptr, ptr [[LIST]], align 4
+// CHECK-NEXT: [[ARGP_NEXT:%.*]] = getelementptr inbounds i8, ptr
[[ARGP_CUR]], i32 8
+// CHECK-NEXT: store ptr [[ARGP_NEXT]], ptr [[LIST]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load <2 x float>, ptr [[ARGP_CUR]], align 4
+// CHECK-NEXT: store <2 x float> [[TMP0]], ptr [[X]], align 8
+// CHECK-NEXT: call void @llvm.va_end.p0(ptr [[LIST]])
+// CHECK-NEXT: [[TMP1:%.*]] = load <2 x float>, ptr [[X]], align 8
+// CHECK-NEXT: ret <2 x float> [[TMP1]]
+//
+__v2sf mmx_vector_test(int z, ...) {
+ __builtin_va_list list;
+ __builtin_va_start(list, z);
+ __v2sf x = __builtin_va_arg(list, __v2sf);
+ __builtin_va_end(list);
+ return x;
+}
+
+// A struct explicitly over-aligned (32) beyond its vector member's own
+// natural alignment (16) realigns only to that member's alignment: GCC's
+// own diagnostic marks its wider behavior here as version-dependent, so
+// this deliberately does not chase it.
+typedef struct __attribute__((aligned(32))) { __v4sf v; }
OveralignedVectorRecord;
+
+// LINUXABI-LABEL: define dso_local void @overaligned_vector_record_test(
+// LINUXABI-SAME: ptr dead_on_unwind noalias writable
sret([[STRUCT_OVERALIGNEDVECTORRECORD:%.*]]) align 32 [[AGG_RESULT:%.*]], i32
noundef [[Z:%.*]], ...) #[[ATTR0]] {
+// LINUXABI-NEXT: [[ENTRY:.*:]]
+// LINUXABI-NEXT: [[RESULT_PTR:%.*]] = alloca ptr, align 4
+// LINUXABI-NEXT: [[Z_ADDR:%.*]] = alloca i32, align 4
+// LINUXABI-NEXT: [[LIST:%.*]] = alloca ptr, align 4
+// LINUXABI-NEXT: store ptr [[AGG_RESULT]], ptr [[RESULT_PTR]], align 4
+// LINUXABI-NEXT: store i32 [[Z]], ptr [[Z_ADDR]], align 4
+// LINUXABI-NEXT: call void @llvm.va_start.p0(ptr [[LIST]])
+// LINUXABI-NEXT: [[ARGP_CUR:%.*]] = load ptr, ptr [[LIST]], align 4
+// LINUXABI-NEXT: [[TMP0:%.*]] = getelementptr inbounds i8, ptr
[[ARGP_CUR]], i32 15
+// LINUXABI-NEXT: [[ARGP_CUR_ALIGNED:%.*]] = call ptr
@llvm.ptrmask.p0.i32(ptr [[TMP0]], i32 -16)
+// LINUXABI-NEXT: [[ARGP_NEXT:%.*]] = getelementptr inbounds i8, ptr
[[ARGP_CUR_ALIGNED]], i32 32
+// LINUXABI-NEXT: store ptr [[ARGP_NEXT]], ptr [[LIST]], align 4
+// LINUXABI-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 32
[[AGG_RESULT]], ptr align 16 [[ARGP_CUR_ALIGNED]], i32 32, i1 false)
+// LINUXABI-NEXT: call void @llvm.va_end.p0(ptr [[LIST]])
+// LINUXABI-NEXT: ret void
+//
+// SYSV-LABEL: define dso_local void @overaligned_vector_record_test(
+// SYSV-SAME: ptr dead_on_unwind noalias writable
sret([[STRUCT_OVERALIGNEDVECTORRECORD:%.*]]) align 32 [[AGG_RESULT:%.*]], i32
noundef [[Z:%.*]], ...) #[[ATTR0]] {
+// SYSV-NEXT: [[ENTRY:.*:]]
+// SYSV-NEXT: [[RESULT_PTR:%.*]] = alloca ptr, align 4
+// SYSV-NEXT: [[Z_ADDR:%.*]] = alloca i32, align 4
+// SYSV-NEXT: [[LIST:%.*]] = alloca ptr, align 4
+// SYSV-NEXT: store ptr [[AGG_RESULT]], ptr [[RESULT_PTR]], align 4
+// SYSV-NEXT: store i32 [[Z]], ptr [[Z_ADDR]], align 4
+// SYSV-NEXT: call void @llvm.va_start.p0(ptr [[LIST]])
+// SYSV-NEXT: [[ARGP_CUR:%.*]] = load ptr, ptr [[LIST]], align 4
+// SYSV-NEXT: [[ARGP_NEXT:%.*]] = getelementptr inbounds i8, ptr
[[ARGP_CUR]], i32 32
+// SYSV-NEXT: store ptr [[ARGP_NEXT]], ptr [[LIST]], align 4
+// SYSV-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 32 [[AGG_RESULT]],
ptr align 4 [[ARGP_CUR]], i32 32, i1 false)
+// SYSV-NEXT: call void @llvm.va_end.p0(ptr [[LIST]])
+// SYSV-NEXT: ret void
+//
+OveralignedVectorRecord overaligned_vector_record_test(int z, ...) {
+ __builtin_va_list list;
+ __builtin_va_start(list, z);
+ OveralignedVectorRecord x = __builtin_va_arg(list, OveralignedVectorRecord);
+ __builtin_va_end(list);
+ return x;
+}
diff --git a/clang/test/CodeGenCXX/x86_32-vaarg.cpp
b/clang/test/CodeGenCXX/x86_32-vaarg.cpp
index 90665652a0286..5b89825ab26a3 100644
--- a/clang/test/CodeGenCXX/x86_32-vaarg.cpp
+++ b/clang/test/CodeGenCXX/x86_32-vaarg.cpp
@@ -1,5 +1,5 @@
// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py
-// RUN: %clang_cc1 -triple i386-linux-gnu -emit-llvm -o - %s | FileCheck %s
+// RUN: %clang_cc1 -triple i386-linux-gnu -Wno-non-pod-varargs -emit-llvm -o -
%s | FileCheck %s
typedef struct {} empty;
@@ -40,3 +40,32 @@ SortOfEmpty test_sort_of_empty(int z, ...) {
__builtin_va_start(list, z);
return __builtin_va_arg(list, SortOfEmpty);
}
+
+// A vector member reached through a base class also triggers realignment
+// on Linux/MinGW/Cygwin, matching real GCC (recursion in
+// getX86VectorRegisterVarArgAlign() walks base classes, not just fields).
+typedef float __v4sf __attribute__((__vector_size__(16)));
+struct VecBase { __v4sf v; };
+struct VecDerived : VecBase { int tag; };
+
+// CHECK-LABEL: @_Z16base_vector_testiz(
+// CHECK-NEXT: entry:
+// CHECK-NEXT: [[RESULT_PTR:%.*]] = alloca ptr, align 4
+// CHECK-NEXT: [[Z_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[LIST:%.*]] = alloca ptr, align 4
+// CHECK-NEXT: store ptr [[AGG_RESULT:%.*]], ptr [[RESULT_PTR]], align 4
+// CHECK-NEXT: store i32 [[Z:%.*]], ptr [[Z_ADDR]], align 4
+// CHECK-NEXT: call void @llvm.va_start.p0(ptr [[LIST]])
+// CHECK-NEXT: [[ARGP_CUR:%.*]] = load ptr, ptr [[LIST]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = getelementptr inbounds i8, ptr [[ARGP_CUR]],
i32 15
+// CHECK-NEXT: [[ARGP_CUR_ALIGNED:%.*]] = call ptr @llvm.ptrmask.p0.i32(ptr
[[TMP0]], i32 -16)
+// CHECK-NEXT: [[ARGP_NEXT:%.*]] = getelementptr inbounds i8, ptr
[[ARGP_CUR_ALIGNED]], i32 32
+// CHECK-NEXT: store ptr [[ARGP_NEXT]], ptr [[LIST]], align 4
+// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 16
[[AGG_RESULT]], ptr align 16 [[ARGP_CUR_ALIGNED]], i32 32, i1 false)
+// CHECK-NEXT: ret void
+//
+VecDerived base_vector_test(int z, ...) {
+ __builtin_va_list list;
+ __builtin_va_start(list, z);
+ return __builtin_va_arg(list, VecDerived);
+}
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits