https://github.com/andykaylor created 
https://github.com/llvm/llvm-project/pull/224138

This adds support for handling homogeneous aggregate arguments in the AArch64 
implementation of the LLVM ABI library.

This required adding a new field to the llvm::abi::Type class to track the 
unadjusted alignment of the field. This meant I needed to make updates to the 
CIR calling convention lowering pass, but AArch64 isn't supported in the CIR 
pass yet, so that part of the change is strictly NFC at this point.

I'm removing the aarch64-pc-windows-msvc run lines from the 
abi-classify-arg-types.cpp test because the new test cases added would land in 
NYI diagnostic in isPermittedToBeHomogeneousAggregate(), and they weren't 
adding any unique coverage.

Assisted-by: Cursor / various models

>From b505aa1422f20e67f75225bf5da6a4e5f9df123d Mon Sep 17 00:00:00 2001
From: Andy Kaylor <[email protected]>
Date: Wed, 19 Aug 2026 14:49:54 -0700
Subject: [PATCH] [LLVMABI][AARCH64] Support homogeneous aggregate arguments

This adds support for handling homogeneous aggregate arguments in the
AArch64 implementation of the LLVM ABI library.

This required adding a new field to the llvm::abi::Type class to track
the unadjusted alignment of the field.

Assisted-by: Cursor / various models
---
 .../Transforms/CallConvLoweringPass.cpp       |   9 +-
 clang/lib/CodeGen/CGCall.cpp                  |   7 +-
 clang/lib/CodeGen/QualTypeMapper.cpp          |  15 +-
 .../CodeGen/AArch64/abi-classify-arg-types.c  | 146 +++++++-
 .../AArch64/abi-classify-arg-types.cpp        |  75 +++-
 clang/test/CodeGen/AArch64/args-hfa.c         |  13 +
 clang/test/CodeGen/AArch64/arguments-hfa-v3.c |   1 +
 llvm/include/llvm/ABI/Types.h                 |  36 +-
 llvm/lib/ABI/Targets/AArch64.cpp              |  31 ++
 llvm/lib/ABI/Targets/X86.cpp                  |   5 +-
 llvm/lib/ABI/Types.cpp                        |  13 +
 llvm/unittests/ABI/AArch64TargetInfoTest.cpp  | 321 ++++++++++++++----
 llvm/unittests/ABI/FunctionInfoTest.cpp       |   1 +
 llvm/unittests/ABI/IRTypeMapperTest.cpp       |   3 +-
 llvm/unittests/ABI/TypesTest.cpp              |  28 ++
 llvm/unittests/ABI/X86TargetInfoTest.cpp      |  24 +-
 16 files changed, 613 insertions(+), 115 deletions(-)

diff --git a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp 
b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
index f51ee6f9169a3..ae5f016fc3083 100644
--- a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
@@ -408,9 +408,12 @@ static const llvm::abi::Type *mapCIRType(mlir::Type type,
         // Mapped with no fields, an empty record reaches Ignore on its own.
         // The size still matters: past two eightbytes SysV says memory 
whatever
         // the content.
+        // TODO: AArch64 needs the unadjusted alignment. We'll need to add that
+        // to RecordLayoutAttr.
         if (recTy.isEmptyForABI())
           return tb.getRecordType(
-              /*Fields=*/{}, sizeBits, align, 
llvm::abi::StructPacking::Default,
+              /*Fields=*/{}, sizeBits, align, /*UnadjustedAlign=*/align,
+              llvm::abi::StructPacking::Default,
               /*BaseClasses=*/{}, /*VirtualBaseClasses=*/{}, flags);
 
         SmallVector<llvm::abi::FieldInfo> fields;
@@ -487,6 +490,7 @@ static const llvm::abi::Type *mapCIRType(mlir::Type type,
                 mapCIRType(variantTy, typeMapper, dl, modOp)));
           }
           return tb.getUnionType(fields, sizeBits, align,
+                                 /*UnadjustedAlign=*/align,
                                  llvm::abi::StructPacking::Default, flags);
         }
 
@@ -521,7 +525,8 @@ static const llvm::abi::Type *mapCIRType(mlir::Type type,
         }
 
         return tb.getRecordType(
-            fields, sizeBits, align, llvm::abi::StructPacking::Default,
+            fields, sizeBits, align, /*UnadjustedAlign=*/align,
+            llvm::abi::StructPacking::Default,
             /*BaseClasses=*/{}, /*VirtualBaseClasses=*/{}, flags);
       })
       .Default([](mlir::Type) -> const llvm::abi::Type * {
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 13036b4cdd58c..44387bbd31d03 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -1027,9 +1027,14 @@ ABIArgInfo CodeGenModule::convertABIArgInfo(const 
llvm::abi::ArgInfo &AbiInfo,
       CoercedType = AbiReverseMapper->convertType(AbiInfo.getCoerceToType());
     if (!CoercedType)
       CoercedType = getTypes().ConvertType(Type);
+    unsigned DirectAlign = 0;
+    if (llvm::MaybeAlign Align = AbiInfo.getDirectAlign())
+      DirectAlign = Align->value();
+    // TODO: Move Padding into the ABIArgInfo struct when we add support for
+    //       targets that need a different setting than we have here.
     return ABIArgInfo::getDirect(CoercedType, AbiInfo.getDirectOffset(),
                                  /*Padding=*/nullptr,
-                                 AbiInfo.getCanBeFlattened());
+                                 AbiInfo.getCanBeFlattened(), DirectAlign);
   }
   case llvm::abi::ArgInfo::Extend: {
     llvm::Type *CoercedType = nullptr;
diff --git a/clang/lib/CodeGen/QualTypeMapper.cpp 
b/clang/lib/CodeGen/QualTypeMapper.cpp
index a6e96c63446f6..5170ffd1ab81c 100644
--- a/clang/lib/CodeGen/QualTypeMapper.cpp
+++ b/clang/lib/CodeGen/QualTypeMapper.cpp
@@ -442,7 +442,8 @@ const llvm::abi::Type 
*QualTypeMapper::convertRecordType(const RecordType *RT) {
   const RecordDecl *RD = RT->getDecl()->getDefinition();
   if (!RD)
     return Builder.getRecordType({}, llvm::TypeSize::getFixed(0),
-                                 llvm::Align(1));
+                                 llvm::Align(1),
+                                 /*UnadjustedAlign=*/llvm::Align(1));
 
   if (RD->isUnion())
     return convertUnionType(RD);
@@ -508,6 +509,8 @@ QualTypeMapper::convertCXXRecordType(const CXXRecordDecl 
*RD) {
   llvm::TypeSize Size =
       llvm::TypeSize::getFixed(Layout.getSize().getQuantity() * 8);
   llvm::Align Alignment = llvm::Align(Layout.getAlignment().getQuantity());
+  llvm::Align UnadjustedAlign =
+      llvm::Align(Layout.getUnadjustedAlignment().getQuantity());
 
   llvm::abi::RecordFlags RecFlags = llvm::abi::RecordFlags::IsCXXRecord;
   if (RD->isPolymorphic())
@@ -517,7 +520,7 @@ QualTypeMapper::convertCXXRecordType(const CXXRecordDecl 
*RD) {
   if (RD->hasFlexibleArrayMember())
     RecFlags |= llvm::abi::RecordFlags::HasFlexibleArrayMember;
 
-  return Builder.getRecordType(Fields, Size, Alignment,
+  return Builder.getRecordType(Fields, Size, Alignment, UnadjustedAlign,
                                llvm::abi::StructPacking::Default, BaseClasses,
                                VirtualBaseClasses, RecFlags);
 }
@@ -556,6 +559,8 @@ QualTypeMapper::convertStructType(const clang::RecordDecl 
*RD) {
   llvm::TypeSize Size =
       llvm::TypeSize::getFixed(Layout.getSize().getQuantity() * 8);
   llvm::Align Alignment = llvm::Align(Layout.getAlignment().getQuantity());
+  llvm::Align UnadjustedAlign =
+      llvm::Align(Layout.getUnadjustedAlignment().getQuantity());
 
   llvm::abi::RecordFlags RecFlags = llvm::abi::RecordFlags::None;
   if (IsCXXRecord)
@@ -565,7 +570,7 @@ QualTypeMapper::convertStructType(const clang::RecordDecl 
*RD) {
   if (RD->hasFlexibleArrayMember())
     RecFlags |= llvm::abi::RecordFlags::HasFlexibleArrayMember;
 
-  return Builder.getRecordType(Fields, Size, Alignment,
+  return Builder.getRecordType(Fields, Size, Alignment, UnadjustedAlign,
                                llvm::abi::StructPacking::Default, {}, {},
                                RecFlags);
 }
@@ -586,6 +591,8 @@ QualTypeMapper::convertUnionType(const clang::RecordDecl 
*RD) {
   llvm::TypeSize Size =
       llvm::TypeSize::getFixed(Layout.getSize().getQuantity() * 8);
   llvm::Align Alignment = llvm::Align(Layout.getAlignment().getQuantity());
+  llvm::Align UnadjustedAlign =
+      llvm::Align(Layout.getUnadjustedAlignment().getQuantity());
 
   llvm::abi::RecordFlags RecFlags = llvm::abi::RecordFlags::None;
   if (RD->hasAttr<TransparentUnionAttr>())
@@ -595,7 +602,7 @@ QualTypeMapper::convertUnionType(const clang::RecordDecl 
*RD) {
   if (isa<CXXRecordDecl>(RD))
     RecFlags |= llvm::abi::RecordFlags::IsCXXRecord;
 
-  return Builder.getUnionType(AllFields, Size, Alignment,
+  return Builder.getUnionType(AllFields, Size, Alignment, UnadjustedAlign,
                               llvm::abi::StructPacking::Default, RecFlags);
 }
 
diff --git a/clang/test/CodeGen/AArch64/abi-classify-arg-types.c 
b/clang/test/CodeGen/AArch64/abi-classify-arg-types.c
index 7f0ba6ce6bfc3..1289e0be4e505 100644
--- a/clang/test/CodeGen/AArch64/abi-classify-arg-types.c
+++ b/clang/test/CodeGen/AArch64/abi-classify-arg-types.c
@@ -1,15 +1,15 @@
-// RUN: %clang_cc1 -triple arm64-apple-ios7.0 -target-abi darwinpcs 
-fenable-matrix -fexperimental-max-bitint-width=1024 -emit-llvm -o - %s | 
FileCheck %s --check-prefixes=CHECK,DARWIN,LONG64
-// RUN: %clang_cc1 -triple arm64-apple-ios7.0 -target-abi darwinpcs 
-fenable-matrix -fexperimental-max-bitint-width=1024 
-fexperimental-abi-lowering -emit-llvm -o - %s 2>&1 | FileCheck %s 
--check-prefixes=CHECK,DARWIN,LONG64 --implicit-check-not="not yet implemented"
-// RUN: %clang_cc1 -triple arm64_32-apple-ios7.0 -target-abi darwinpcs 
-fenable-matrix -fexperimental-max-bitint-width=1024 -emit-llvm -o - %s | 
FileCheck %s --check-prefixes=CHECK,DARWIN,LONG32
-// RUN: %clang_cc1 -triple arm64_32-apple-ios7.0 -target-abi darwinpcs 
-fenable-matrix -fexperimental-max-bitint-width=1024 
-fexperimental-abi-lowering -emit-llvm -o - %s 2>&1 | FileCheck %s 
--check-prefixes=CHECK,DARWIN,LONG32 --implicit-check-not="not yet implemented"
-// RUN: %clang_cc1 -triple aarch64-linux-gnu -fenable-matrix 
-fexperimental-max-bitint-width=1024 -emit-llvm -o - %s | FileCheck %s 
--check-prefixes=CHECK,AAPCS,LONG64
-// RUN: %clang_cc1 -triple aarch64-linux-gnu -fenable-matrix 
-fexperimental-max-bitint-width=1024 -fexperimental-abi-lowering -emit-llvm -o 
- %s 2>&1 | FileCheck %s --check-prefixes=CHECK,AAPCS,LONG64 
--implicit-check-not="not yet implemented"
-// RUN: %clang_cc1 -triple aarch64_be-linux-gnu -fenable-matrix 
-fexperimental-max-bitint-width=1024 -emit-llvm -o - %s | FileCheck %s 
--check-prefixes=CHECK,AAPCS,LONG64
-// RUN: %clang_cc1 -triple aarch64_be-linux-gnu -fenable-matrix 
-fexperimental-max-bitint-width=1024 -fexperimental-abi-lowering -emit-llvm -o 
- %s 2>&1 | FileCheck %s --check-prefixes=CHECK,AAPCS,LONG64 
--implicit-check-not="not yet implemented"
-// RUN: %clang_cc1 -triple aarch64-pc-windows-msvc -fenable-matrix 
-fexperimental-max-bitint-width=1024 -emit-llvm -o - %s | FileCheck %s 
--check-prefixes=CHECK,AAPCS,LONG32
-// RUN: %clang_cc1 -triple aarch64-pc-windows-msvc -fenable-matrix 
-fexperimental-max-bitint-width=1024 -fexperimental-abi-lowering -emit-llvm -o 
- %s 2>&1 | FileCheck %s --check-prefixes=CHECK,AAPCS,LONG32 
--implicit-check-not="not yet implemented"
-// RUN: %clang_cc1 -triple arm64ec-pc-windows-msvc -fenable-matrix 
-fexperimental-max-bitint-width=1024 -emit-llvm -o - %s | FileCheck %s 
--check-prefixes=CHECK,AAPCS,LONG32
-// RUN: %clang_cc1 -triple arm64ec-pc-windows-msvc -fenable-matrix 
-fexperimental-max-bitint-width=1024 -fexperimental-abi-lowering -emit-llvm -o 
- %s 2>&1 | FileCheck %s --check-prefixes=CHECK,AAPCS,LONG32 
--implicit-check-not="not yet implemented"
+// RUN: %clang_cc1 -triple arm64-apple-ios7.0 -target-abi darwinpcs 
-fenable-matrix -fexperimental-max-bitint-width=1024 -emit-llvm -o - %s | 
FileCheck %s --check-prefixes=CHECK,DARWIN,LONG64,NOHFAALIGN
+// RUN: %clang_cc1 -triple arm64-apple-ios7.0 -target-abi darwinpcs 
-fenable-matrix -fexperimental-max-bitint-width=1024 
-fexperimental-abi-lowering -emit-llvm -o - %s 2>&1 | FileCheck %s 
--check-prefixes=CHECK,DARWIN,LONG64,NOHFAALIGN --implicit-check-not="not yet 
implemented"
+// RUN: %clang_cc1 -triple arm64_32-apple-ios7.0 -target-abi darwinpcs 
-fenable-matrix -fexperimental-max-bitint-width=1024 -emit-llvm -o - %s | 
FileCheck %s --check-prefixes=CHECK,DARWIN,LONG32,NOHFAALIGN
+// RUN: %clang_cc1 -triple arm64_32-apple-ios7.0 -target-abi darwinpcs 
-fenable-matrix -fexperimental-max-bitint-width=1024 
-fexperimental-abi-lowering -emit-llvm -o - %s 2>&1 | FileCheck %s 
--check-prefixes=CHECK,DARWIN,LONG32,NOHFAALIGN --implicit-check-not="not yet 
implemented"
+// RUN: %clang_cc1 -triple aarch64-linux-gnu -fenable-matrix 
-fexperimental-max-bitint-width=1024 -emit-llvm -o - %s | FileCheck %s 
--check-prefixes=CHECK,AAPCS,LONG64,AAPCS64
+// RUN: %clang_cc1 -triple aarch64-linux-gnu -fenable-matrix 
-fexperimental-max-bitint-width=1024 -fexperimental-abi-lowering -emit-llvm -o 
- %s 2>&1 | FileCheck %s --check-prefixes=CHECK,AAPCS,LONG64,AAPCS64 
--implicit-check-not="not yet implemented"
+// RUN: %clang_cc1 -triple aarch64_be-linux-gnu -fenable-matrix 
-fexperimental-max-bitint-width=1024 -emit-llvm -o - %s | FileCheck %s 
--check-prefixes=CHECK,AAPCS,LONG64,AAPCS64
+// RUN: %clang_cc1 -triple aarch64_be-linux-gnu -fenable-matrix 
-fexperimental-max-bitint-width=1024 -fexperimental-abi-lowering -emit-llvm -o 
- %s 2>&1 | FileCheck %s --check-prefixes=CHECK,AAPCS,LONG64,AAPCS64 
--implicit-check-not="not yet implemented"
+// RUN: %clang_cc1 -triple aarch64-pc-windows-msvc -fenable-matrix 
-fexperimental-max-bitint-width=1024 -emit-llvm -o - %s | FileCheck %s 
--check-prefixes=CHECK,AAPCS,LONG32,NOHFAALIGN
+// RUN: %clang_cc1 -triple aarch64-pc-windows-msvc -fenable-matrix 
-fexperimental-max-bitint-width=1024 -fexperimental-abi-lowering -emit-llvm -o 
- %s 2>&1 | FileCheck %s --check-prefixes=CHECK,AAPCS,LONG32,NOHFAALIGN 
--implicit-check-not="not yet implemented"
+// RUN: %clang_cc1 -triple arm64ec-pc-windows-msvc -fenable-matrix 
-fexperimental-max-bitint-width=1024 -emit-llvm -o - %s | FileCheck %s 
--check-prefixes=CHECK,AAPCS,LONG32,NOHFAALIGN
+// RUN: %clang_cc1 -triple arm64ec-pc-windows-msvc -fenable-matrix 
-fexperimental-max-bitint-width=1024 -fexperimental-abi-lowering -emit-llvm -o 
- %s 2>&1 | FileCheck %s --check-prefixes=CHECK,AAPCS,LONG32,NOHFAALIGN 
--implicit-check-not="not yet implemented"
 
 // This test is verifying that the LLVM ABI library classifies argument types 
in
 // the same way that Clang does without the library.
@@ -110,3 +110,125 @@ void arg_bitint128(_BitInt(128) x) {}
 
 void arg_bitint129(_BitInt(129) x) {}
 // CHECK: define{{.*}} void @arg_bitint129(ptr nofreeobj noundef align 16 
dead_on_return dereferenceable(32) %{{.*}})
+
+// Homogeneous floating-point aggregates are coerced to an array of the base
+// type. AAPCS sets alignstack from unadjusted alignment (8, or 16 if the
+// unadjusted alignment is at least 16). DarwinPCS and Win64 do not.
+
+typedef struct {
+  float a, b;
+} HFA2f;
+void arg_hfa2f(HFA2f h) {}
+// AAPCS64: define{{.*}} void @arg_hfa2f([2 x float] alignstack(8) %{{.*}})
+// NOHFAALIGN: define{{.*}} void @arg_hfa2f([2 x float] %{{.*}})
+
+typedef struct {
+  double a, b, c, d;
+} HFA4d;
+void arg_hfa4d(HFA4d h) {}
+// AAPCS64: define{{.*}} void @arg_hfa4d([4 x double] alignstack(8) %{{.*}})
+// NOHFAALIGN: define{{.*}} void @arg_hfa4d([4 x double] %{{.*}})
+
+typedef struct {
+  float v[3];
+} HFA3arr;
+void arg_hfa3arr(HFA3arr h) {}
+// AAPCS64: define{{.*}} void @arg_hfa3arr([3 x float] alignstack(8) %{{.*}})
+// NOHFAALIGN: define{{.*}} void @arg_hfa3arr([3 x float] %{{.*}})
+
+typedef struct {
+  _Float16 a, b;
+} HFA2h;
+void arg_hfa2h(HFA2h h) {}
+// AAPCS64: define{{.*}} void @arg_hfa2h([2 x half] alignstack(8) %{{.*}})
+// NOHFAALIGN: define{{.*}} void @arg_hfa2h([2 x half] %{{.*}})
+
+typedef struct {
+  HFA2f inner;
+  float c;
+} HFANested;
+void arg_hfa_nested(HFANested h) {}
+// AAPCS64: define{{.*}} void @arg_hfa_nested([3 x float] alignstack(8) 
%{{.*}})
+// NOHFAALIGN: define{{.*}} void @arg_hfa_nested([3 x float] %{{.*}})
+
+typedef struct {
+  int : 0;
+  float a, b;
+} HFAZeroBF;
+void arg_hfa_zerobf(HFAZeroBF h) {}
+// AAPCS64: define{{.*}} void @arg_hfa_zerobf([2 x float] alignstack(8) 
%{{.*}})
+// NOHFAALIGN: define{{.*}} void @arg_hfa_zerobf([2 x float] %{{.*}})
+
+typedef union {
+  float a;
+  float v[3];
+} HFAUnion;
+void arg_hfa_union(HFAUnion h) {}
+// AAPCS64: define{{.*}} void @arg_hfa_union([3 x float] alignstack(8) %{{.*}})
+// NOHFAALIGN: define{{.*}} void @arg_hfa_union([3 x float] %{{.*}})
+
+void arg_complex_float(_Complex float c) {}
+// AAPCS64: define{{.*}} void @arg_complex_float([2 x float] {{(noundef 
)?}}alignstack(8) %{{.*}})
+// NOHFAALIGN: define{{.*}} void @arg_complex_float([2 x float] {{(noundef 
)?}}%{{.*}})
+
+typedef float f32x2 __attribute__((vector_size(8)));
+typedef float f32x4 __attribute__((vector_size(16)));
+
+typedef struct {
+  f32x2 a, b;
+} HVA2x64;
+void arg_hva2x64(HVA2x64 h) {}
+// AAPCS64: define{{.*}} void @arg_hva2x64([2 x <2 x float>] alignstack(8) 
%{{.*}})
+// NOHFAALIGN: define{{.*}} void @arg_hva2x64([2 x <2 x float>] %{{.*}})
+
+typedef struct {
+  f32x4 a, b;
+} HVA2x128;
+void arg_hva2x128(HVA2x128 h) {}
+// AAPCS64: define{{.*}} void @arg_hva2x128([2 x <4 x float>] alignstack(16) 
%{{.*}})
+// NOHFAALIGN: define{{.*}} void @arg_hva2x128([2 x <4 x float>] %{{.*}})
+
+// Record-level aligned(16) on a 16-byte HFA raises ABI alignment to 16 without
+// adding padding, so the type is still homogeneous. Unadjusted alignment is
+// still 8, so AAPCS must use alignstack(8), not 16.
+typedef struct __attribute__((aligned(16))) {
+  double a, b;
+} OveralignedHFA;
+void arg_overaligned_hfa(OveralignedHFA h) {}
+// AAPCS64: define{{.*}} void @arg_overaligned_hfa([2 x double] alignstack(8) 
%{{.*}})
+// NOHFAALIGN: define{{.*}} void @arg_overaligned_hfa([2 x double] %{{.*}})
+
+// aligned(32) on a 32-byte HFA likewise stays homogeneous. Unadjusted
+// alignment is 8, so AAPCS must not take the 16-byte cap.
+typedef struct __attribute__((aligned(32))) {
+  double a, b, c, d;
+} Overaligned32HFA;
+void arg_overaligned32_hfa(Overaligned32HFA h) {}
+// AAPCS64: define{{.*}} void @arg_overaligned32_hfa([4 x double] 
alignstack(8) %{{.*}})
+// NOHFAALIGN: define{{.*}} void @arg_overaligned32_hfa([4 x double] %{{.*}})
+
+// The unadjusted alignment of a union is tracked the same way. The widest
+// member is already 16 bytes, so aligned(16) adds no padding.
+typedef union __attribute__((aligned(16))) {
+  double a;
+  double v[2];
+} OveralignedUnionHFA;
+void arg_overaligned_union_hfa(OveralignedUnionHFA u) {}
+// AAPCS64: define{{.*}} void @arg_overaligned_union_hfa([2 x double] 
alignstack(8) %{{.*}})
+// NOHFAALIGN: define{{.*}} void @arg_overaligned_union_hfa([2 x double] 
%{{.*}})
+
+// Field alignment is part of unadjusted alignment, so AAPCS uses 16.
+typedef struct {
+  __attribute__((aligned(16))) double v[2];
+} FieldAlignedHFA;
+void arg_field_aligned_hfa(FieldAlignedHFA h) {}
+// AAPCS64: define{{.*}} void @arg_field_aligned_hfa([2 x double] 
alignstack(16) %{{.*}})
+// NOHFAALIGN: define{{.*}} void @arg_field_aligned_hfa([2 x double] %{{.*}})
+
+// Unadjusted alignment of 32 is capped at 16.
+typedef struct {
+  __attribute__((aligned(32))) double v[4];
+} FieldAligned32HFA;
+void arg_field_aligned32_hfa(FieldAligned32HFA h) {}
+// AAPCS64: define{{.*}} void @arg_field_aligned32_hfa([4 x double] 
alignstack(16) %{{.*}})
+// NOHFAALIGN: define{{.*}} void @arg_field_aligned32_hfa([4 x double] %{{.*}})
diff --git a/clang/test/CodeGen/AArch64/abi-classify-arg-types.cpp 
b/clang/test/CodeGen/AArch64/abi-classify-arg-types.cpp
index 3259e08eb65b8..0c883cf404212 100644
--- a/clang/test/CodeGen/AArch64/abi-classify-arg-types.cpp
+++ b/clang/test/CodeGen/AArch64/abi-classify-arg-types.cpp
@@ -1,13 +1,11 @@
-// RUN: %clang_cc1 -triple arm64-apple-ios7.0 -target-abi darwinpcs -emit-llvm 
-o - %s | FileCheck %s
-// RUN: %clang_cc1 -triple arm64-apple-ios7.0 -target-abi darwinpcs 
-fexperimental-abi-lowering -emit-llvm -o - %s 2>&1 | FileCheck %s 
--implicit-check-not="not yet implemented"
-// RUN: %clang_cc1 -triple arm64_32-apple-ios7.0 -target-abi darwinpcs 
-emit-llvm -o - %s | FileCheck %s
-// RUN: %clang_cc1 -triple arm64_32-apple-ios7.0 -target-abi darwinpcs 
-fexperimental-abi-lowering -emit-llvm -o - %s 2>&1 | FileCheck %s 
--implicit-check-not="not yet implemented"
-// RUN: %clang_cc1 -triple aarch64-linux-gnu -emit-llvm -o - %s | FileCheck %s
-// RUN: %clang_cc1 -triple aarch64-linux-gnu -fexperimental-abi-lowering 
-emit-llvm -o - %s 2>&1 | FileCheck %s --implicit-check-not="not yet 
implemented"
-// RUN: %clang_cc1 -triple aarch64_be-linux-gnu -emit-llvm -o - %s | FileCheck 
%s
-// RUN: %clang_cc1 -triple aarch64_be-linux-gnu -fexperimental-abi-lowering 
-emit-llvm -o - %s 2>&1 | FileCheck %s --implicit-check-not="not yet 
implemented"
-// RUN: %clang_cc1 -triple aarch64-pc-windows-msvc -emit-llvm -o - %s | 
FileCheck %s
-// RUN: %clang_cc1 -triple aarch64-pc-windows-msvc -fexperimental-abi-lowering 
-emit-llvm -o - %s 2>&1 | FileCheck %s --implicit-check-not="not yet 
implemented"
+// RUN: %clang_cc1 -triple arm64-apple-ios7.0 -target-abi darwinpcs -std=c++20 
-emit-llvm -o - %s | FileCheck %s --check-prefixes=CHECK,NOHFAALIGN
+// RUN: %clang_cc1 -triple arm64-apple-ios7.0 -target-abi darwinpcs -std=c++20 
-fexperimental-abi-lowering -emit-llvm -o - %s 2>&1 | FileCheck %s 
--check-prefixes=CHECK,NOHFAALIGN --implicit-check-not="not yet implemented"
+// RUN: %clang_cc1 -triple arm64_32-apple-ios7.0 -target-abi darwinpcs 
-std=c++20 -emit-llvm -o - %s | FileCheck %s --check-prefixes=CHECK,NOHFAALIGN
+// RUN: %clang_cc1 -triple arm64_32-apple-ios7.0 -target-abi darwinpcs 
-std=c++20 -fexperimental-abi-lowering -emit-llvm -o - %s 2>&1 | FileCheck %s 
--check-prefixes=CHECK,NOHFAALIGN --implicit-check-not="not yet implemented"
+// RUN: %clang_cc1 -triple aarch64-linux-gnu -std=c++20 -emit-llvm -o - %s | 
FileCheck %s --check-prefixes=CHECK,AAPCS64
+// RUN: %clang_cc1 -triple aarch64-linux-gnu -std=c++20 
-fexperimental-abi-lowering -emit-llvm -o - %s 2>&1 | FileCheck %s 
--check-prefixes=CHECK,AAPCS64 --implicit-check-not="not yet implemented"
+// RUN: %clang_cc1 -triple aarch64_be-linux-gnu -std=c++20 -emit-llvm -o - %s 
| FileCheck %s --check-prefixes=CHECK,AAPCS64
+// RUN: %clang_cc1 -triple aarch64_be-linux-gnu -std=c++20 
-fexperimental-abi-lowering -emit-llvm -o - %s 2>&1 | FileCheck %s 
--check-prefixes=CHECK,AAPCS64 --implicit-check-not="not yet implemented"
 
 // This test is verifying that the LLVM ABI library classifies C++ record
 // arguments that cannot be passed in registers the same way Clang does without
@@ -44,4 +42,61 @@ void arg_nontrivial_dtor_and_copy(NonTrivialDtorAndCopy a) {}
 void arg_explicit_copy(ExplicitCopy a) {}
 // CHECK: define{{.*}} void @arg_explicit_copy(ptr nofreeobj noundef align 2 
dead_on_return dereferenceable(2) %{{.*}})
 
+// Homogeneous aggregates that can pass in registers are coerced to an array of
+// the base type, including inherited members, nested records, and zero-length
+// bitfields.
+
+struct HFA2f {
+  float a, b;
+};
+void arg_hfa2f(HFA2f h) {}
+// AAPCS64: define{{.*}} void @arg_hfa2f([2 x float] alignstack(8) %{{.*}})
+// NOHFAALIGN: define{{.*}} void @arg_hfa2f([2 x float] %{{.*}})
+
+struct HFABase {
+  float a;
+};
+struct HFADerived : HFABase {
+  float b;
+};
+void arg_hfa_derived(HFADerived h) {}
+// AAPCS64: define{{.*}} void @arg_hfa_derived([2 x float] alignstack(8) 
%{{.*}})
+// NOHFAALIGN: define{{.*}} void @arg_hfa_derived([2 x float] %{{.*}})
+
+struct HFANested {
+  HFA2f inner;
+  float c;
+};
+void arg_hfa_nested(HFANested h) {}
+// AAPCS64: define{{.*}} void @arg_hfa_nested([3 x float] alignstack(8) 
%{{.*}})
+// NOHFAALIGN: define{{.*}} void @arg_hfa_nested([3 x float] %{{.*}})
+
+struct HFAZeroBF {
+  int : 0;
+  float a, b;
+};
+void arg_hfa_zerobf(HFAZeroBF h) {}
+// AAPCS64: define{{.*}} void @arg_hfa_zerobf([2 x float] alignstack(8) 
%{{.*}})
+// NOHFAALIGN: define{{.*}} void @arg_hfa_zerobf([2 x float] %{{.*}})
+
+struct __attribute__((aligned(16))) OveralignedHFA {
+  double a, b;
+};
+void arg_overaligned_hfa(OveralignedHFA h) {}
+// AAPCS64: define{{.*}} void @arg_overaligned_hfa([2 x double] alignstack(8) 
%{{.*}})
+// NOHFAALIGN: define{{.*}} void @arg_overaligned_hfa([2 x double] %{{.*}})
+
+// A record laid out with a base class tracks unadjusted alignment the same
+// way. The base and the field already fill 16 bytes, so aligned(16) adds no
+// padding and the type stays homogeneous.
+struct DoubleBase {
+  double a;
+};
+struct __attribute__((aligned(16))) OveralignedDerivedHFA : DoubleBase {
+  double b;
+};
+void arg_overaligned_derived_hfa(OveralignedDerivedHFA d) {}
+// AAPCS64: define{{.*}} void @arg_overaligned_derived_hfa([2 x double] 
alignstack(8) %{{.*}})
+// NOHFAALIGN: define{{.*}} void @arg_overaligned_derived_hfa([2 x double] 
%{{.*}})
+
 }
diff --git a/clang/test/CodeGen/AArch64/args-hfa.c 
b/clang/test/CodeGen/AArch64/args-hfa.c
index 75dfd88051664..4d90fc9ef35d2 100644
--- a/clang/test/CodeGen/AArch64/args-hfa.c
+++ b/clang/test/CodeGen/AArch64/args-hfa.c
@@ -1,6 +1,9 @@
 // RUN: %clang_cc1 -triple aarch64 -emit-llvm -o - %s | FileCheck %s 
--check-prefixes=CHECK,CHECK-AAPCS
+// RUN: %clang_cc1 -triple aarch64 -fexperimental-abi-lowering -emit-llvm -o - 
%s 2>&1 | FileCheck %s --check-prefixes=CHECK,CHECK-AAPCS 
--implicit-check-not="not yet implemented"
 // RUN: %clang_cc1 -triple arm64-apple-ios7.0 -target-abi darwinpcs -emit-llvm 
-o - %s | FileCheck %s --check-prefixes=CHECK,CHECK-DARWIN
+// RUN: %clang_cc1 -triple arm64-apple-ios7.0 -target-abi darwinpcs 
-fexperimental-abi-lowering -emit-llvm -o - %s 2>&1 | FileCheck %s 
--check-prefixes=CHECK,CHECK-DARWIN --implicit-check-not="not yet implemented"
 // RUN: %clang_cc1 -triple aarch64-linux-gnu -emit-llvm -o - -x c %s | 
FileCheck %s --check-prefixes=CHECK,CHECK-AAPCS
+// RUN: %clang_cc1 -triple aarch64-linux-gnu -fexperimental-abi-lowering 
-emit-llvm -o - -x c %s 2>&1 | FileCheck %s --check-prefixes=CHECK,CHECK-AAPCS 
--implicit-check-not="not yet implemented"
 
 typedef struct {
   float v[2];
@@ -70,3 +73,13 @@ double f3_call(void) {
   S3 h = {1.0, 2.0};
   return f3(h);
 }
+
+void sink(int n, ...);
+
+// CHECK: define{{.*}} void @f4_call()
+// CHECK-AAPCS:  call void (i32, ...) @sink(i32 noundef 1, [2 x double] 
alignstack(8) %0)
+// CHECK-DARWIN: call void (i32, ...) @sink(i32 noundef 1, [2 x double] %0)
+void f4_call(void) {
+  S1 h = {1.0, 2.0};
+  sink(1, h);
+}
diff --git a/clang/test/CodeGen/AArch64/arguments-hfa-v3.c 
b/clang/test/CodeGen/AArch64/arguments-hfa-v3.c
index 4e8cc10b5e2e1..c9c6ddd5f746d 100644
--- a/clang/test/CodeGen/AArch64/arguments-hfa-v3.c
+++ b/clang/test/CodeGen/AArch64/arguments-hfa-v3.c
@@ -1,4 +1,5 @@
 // RUN: %clang_cc1 -triple arm64-apple-ios -target-feature +neon -target-abi 
darwinpcs -emit-llvm -o - %s | FileCheck %s
+// RUN: %clang_cc1 -triple arm64-apple-ios -target-feature +neon -target-abi 
darwinpcs -fexperimental-abi-lowering -emit-llvm -o - %s 2>&1 | FileCheck %s 
--implicit-check-not="not yet implemented"
 
 typedef __attribute__((__ext_vector_type__(16))) signed char int8x16_t;
 typedef __attribute__((__ext_vector_type__(3))) float float32x3_t;
diff --git a/llvm/include/llvm/ABI/Types.h b/llvm/include/llvm/ABI/Types.h
index 5145c201ff1b9..4980dfb6e7428 100644
--- a/llvm/include/llvm/ABI/Types.h
+++ b/llvm/include/llvm/ABI/Types.h
@@ -61,15 +61,24 @@ class Type {
   TypeKind Kind;
   TypeSize SizeInBits;
   Align ABIAlignment;
+  Align UnadjustedAlignment;
 
   Type(TypeKind K, TypeSize SizeInBits, Align ABIAlign)
-      : Kind(K), SizeInBits(SizeInBits), ABIAlignment(ABIAlign) {}
+      : Type(K, SizeInBits, ABIAlign, ABIAlign) {}
+  Type(TypeKind K, TypeSize SizeInBits, Align ABIAlign, Align UnadjustedAlign)
+      : Kind(K), SizeInBits(SizeInBits), ABIAlignment(ABIAlign),
+        UnadjustedAlignment(UnadjustedAlign) {}
 
 public:
   TypeKind getKind() const { return Kind; }
   TypeSize getSizeInBits() const { return SizeInBits; }
   Align getAlignment() const { return ABIAlignment; }
 
+  /// Alignment before record-level adjustments such as aligned attributes.
+  /// Equal to getAlignment() unless a distinct unadjusted alignment was
+  /// provided when the type was created.
+  Align getUnadjustedAlignment() const { return UnadjustedAlignment; }
+
   TypeSize getTypeAllocSize() const {
     return alignTo(getTypeStoreSize(), getAlignment().value());
   }
@@ -86,6 +95,8 @@ class Type {
   bool isMemberPointer() const { return Kind == TypeKind::MemberPointer; }
   bool isComplex() const { return Kind == TypeKind::Complex; }
   bool isZeroSize() const { return getSizeInBits().isZero(); }
+
+  bool isSVESizelessType() const;
 };
 
 class VoidType : public Type {
@@ -359,12 +370,12 @@ class RecordType : public Type {
 
 public:
   RecordType(ArrayRef<FieldInfo> StructFields, ArrayRef<FieldInfo> Bases,
-             ArrayRef<FieldInfo> VBases, TypeSize Size, Align Align,
-             StructPacking Pack = StructPacking::Default,
+             ArrayRef<FieldInfo> VBases, TypeSize Size, Align ABIAlign,
+             Align UnadjustedAlign, StructPacking Pack = 
StructPacking::Default,
              RecordFlags RecFlags = RecordFlags::None)
-      : Type(TypeKind::Record, Size, Align), Fields(StructFields),
-        BaseClasses(Bases), VirtualBaseClasses(VBases), Packing(Pack),
-        Flags(RecFlags) {}
+      : Type(TypeKind::Record, Size, ABIAlign, UnadjustedAlign),
+        Fields(StructFields), BaseClasses(Bases), VirtualBaseClasses(VBases),
+        Packing(Pack), Flags(RecFlags) {}
   uint32_t getNumFields() const { return Fields.size(); }
   StructPacking getPacking() const { return Packing; }
 
@@ -486,7 +497,7 @@ class TypeBuilder {
   }
 
   const RecordType *getRecordType(ArrayRef<FieldInfo> Fields, TypeSize Size,
-                                  Align Align,
+                                  Align ABIAlign, Align UnadjustedAlign,
                                   StructPacking Pack = StructPacking::Default,
                                   ArrayRef<FieldInfo> BaseClasses = {},
                                   ArrayRef<FieldInfo> VirtualBaseClasses = {},
@@ -512,11 +523,12 @@ class TypeBuilder {
     ArrayRef<FieldInfo> VBasesRef(VBaseArray, VirtualBaseClasses.size());
 
     return new (Allocator.Allocate<RecordType>())
-        RecordType(FieldsRef, BasesRef, VBasesRef, Size, Align, Pack, 
RecFlags);
+        RecordType(FieldsRef, BasesRef, VBasesRef, Size, ABIAlign,
+                   UnadjustedAlign, Pack, RecFlags);
   }
 
   const RecordType *getUnionType(ArrayRef<FieldInfo> Fields, TypeSize Size,
-                                 Align Align,
+                                 Align ABIAlign, Align UnadjustedAlign,
                                  StructPacking Pack = StructPacking::Default,
                                  RecordFlags RecFlags = RecordFlags::None) {
     FieldInfo *FieldArray = Allocator.Allocate<FieldInfo>(Fields.size());
@@ -529,9 +541,9 @@ class TypeBuilder {
 
     ArrayRef<FieldInfo> FieldsRef(FieldArray, Fields.size());
 
-    return new (Allocator.Allocate<RecordType>())
-        RecordType(FieldsRef, ArrayRef<FieldInfo>(), ArrayRef<FieldInfo>(),
-                   Size, Align, Pack, RecFlags | RecordFlags::IsUnion);
+    return new (Allocator.Allocate<RecordType>()) RecordType(
+        FieldsRef, ArrayRef<FieldInfo>(), ArrayRef<FieldInfo>(), Size, 
ABIAlign,
+        UnadjustedAlign, Pack, RecFlags | RecordFlags::IsUnion);
   }
 
   const ComplexType *getComplexType(const Type *ElementType, Align Align) {
diff --git a/llvm/lib/ABI/Targets/AArch64.cpp b/llvm/lib/ABI/Targets/AArch64.cpp
index 9faea71ec9b51..07ab2fbe894c3 100644
--- a/llvm/lib/ABI/Targets/AArch64.cpp
+++ b/llvm/lib/ABI/Targets/AArch64.cpp
@@ -144,6 +144,37 @@ ArgInfo AArch64TargetInfo::classifyArgumentType(
                                            RecordArgABI::RAA_DirectInMemory);
   }
 
+  TypeSize TySize = Ty->getSizeInBits();
+  uint64_t Size = TySize.isFixed() ? TySize.getFixedValue() : 0;
+  const auto *RT = dyn_cast<RecordType>(Ty);
+  if (!Ty->isSVESizelessType() && ((RT && RT->isEmpty()) || Size == 0)) {
+    reportNYI("Empty record argument handling");
+    return ArgInfo::getIgnore();
+  }
+
+  // Homogeneous Floating-point Aggregates (HFAs) need to be expanded.
+  const Type *Base = nullptr;
+  uint64_t Members = 0;
+  bool IsWin64 = Opts.Kind == AArch64ABIKind::Win64 ||
+                 CallingConvention == llvm::CallingConv::Win64;
+  bool IsWinVariadic = IsWin64 && IsVariadicFn;
+  // In variadic functions on Windows, all composite types are treated alike,
+  // no special handling of HFAs/HVAs.
+  if (!IsWinVariadic && isHomogeneousAggregate(Ty, Base, Members)) {
+    NSRN = std::min(NSRN + Members, uint64_t(8));
+    uint64_t BaseAllocSizeInBits = Base->getTypeAllocSize().getFixedValue() * 
8;
+    const Type *CoerceTy =
+        TB.getArrayType(Base, Members, Members * BaseAllocSizeInBits);
+    if (Opts.Kind != AArch64ABIKind::AAPCS)
+      return ArgInfo::getDirect(CoerceTy);
+
+    // For HFAs/HVAs, cap the argument alignment to 16, otherwise
+    // set it to 8 according to the AAPCS64 document.
+    unsigned TyAlign = Ty->getUnadjustedAlignment().value();
+    TyAlign = (TyAlign >= 16) ? 16 : 8;
+    return ArgInfo::getDirect(CoerceTy, /*Offset=*/0, llvm::Align(TyAlign));
+  }
+
   reportNYI("Aggregate argument type handling");
   return ArgInfo::getIgnore();
 }
diff --git a/llvm/lib/ABI/Targets/X86.cpp b/llvm/lib/ABI/Targets/X86.cpp
index 08a5e20cb9070..1d4f608f9a66b 100644
--- a/llvm/lib/ABI/Targets/X86.cpp
+++ b/llvm/lib/ABI/Targets/X86.cpp
@@ -805,7 +805,8 @@ ArgInfo X86_64TargetInfo::classifyReturnType(const Type 
*RetTy) const {
       const Type *X87Type =
           TB.getFloatType(APFloat::x87DoubleExtended(), Align(16));
       FieldInfo Fields[] = {FieldInfo(X87Type, 0), FieldInfo(X87Type, 80)};
-      ResType = TB.getRecordType(Fields, TypeSize::getFixed(160), Align(16));
+      ResType = TB.getRecordType(Fields, TypeSize::getFixed(160), Align(16),
+                                 /*UnadjustedAlign=*/Align(16));
     }
     break;
   }
@@ -923,7 +924,7 @@ const Type *X86_64TargetInfo::createPairType(const Type *Lo,
   uint64_t PairSizeInBits =
       Fields[1].OffsetInBits + Hi->getSizeInBits().getFixedValue();
   return TB.getRecordType(Fields, TypeSize::getFixed(PairSizeInBits), Align(8),
-                          StructPacking::Default);
+                          /*UnadjustedAlign=*/Align(8), 
StructPacking::Default);
 }
 
 static bool bitsContainNoUserData(const Type *Ty, unsigned StartBit,
diff --git a/llvm/lib/ABI/Types.cpp b/llvm/lib/ABI/Types.cpp
index 0ab3e2b17f7fa..1d6aa70cb1100 100644
--- a/llvm/lib/ABI/Types.cpp
+++ b/llvm/lib/ABI/Types.cpp
@@ -12,6 +12,19 @@
 using namespace llvm;
 using namespace llvm::abi;
 
+bool llvm::abi::Type::isSVESizelessType() const {
+  if (getKind() == TypeKind::Vector) {
+    const VectorType *VT = static_cast<const VectorType *>(this);
+    return VT->isSVEType() && VT->isScalable();
+  }
+  if (getKind() == TypeKind::Tuple) {
+    const VectorType *VT =
+        static_cast<const TupleType *>(this)->getVectorType();
+    return VT->isSVEType() && VT->isScalable();
+  }
+  return false;
+}
+
 bool RecordType::isEmpty() const {
   if (hasFlexibleArrayMember())
     return false;
diff --git a/llvm/unittests/ABI/AArch64TargetInfoTest.cpp 
b/llvm/unittests/ABI/AArch64TargetInfoTest.cpp
index affa187ae7dad..2c140d81c420e 100644
--- a/llvm/unittests/ABI/AArch64TargetInfoTest.cpp
+++ b/llvm/unittests/ABI/AArch64TargetInfoTest.cpp
@@ -104,12 +104,13 @@ class AArch64TargetInfoTest : public ::testing::Test {
   }
 
   const ABIType *makeRecord(llvm::ArrayRef<FieldInfo> Fields, uint64_t 
SizeBits,
-                            llvm::Align Align,
+                            llvm::Align Align, llvm::Align UnadjustedAlign,
                             RecordFlags Flags = 
RecordFlags::CanPassInRegisters,
                             llvm::ArrayRef<FieldInfo> Bases = {},
                             llvm::ArrayRef<FieldInfo> VBases = {}) {
     return TB.getRecordType(Fields, llvm::TypeSize::getFixed(SizeBits), Align,
-                            StructPacking::Default, Bases, VBases, Flags);
+                            UnadjustedAlign, StructPacking::Default, Bases,
+                            VBases, Flags);
   }
 };
 
@@ -400,6 +401,49 @@ TEST_F(AArch64TargetInfoTest, 
ClassifyArgumentScalarsDirectWin64) {
   }
 }
 
+static void expectNaturalAlignIndirect(const ArgInfo &Info,
+                                       llvm::Align ExpectedAlign, bool ByVal) {
+  EXPECT_TRUE(Info.isIndirect());
+  EXPECT_EQ(Info.getIndirectAlign(), ExpectedAlign);
+  EXPECT_EQ(Info.getIndirectByVal(), ByVal);
+}
+
+static void expectHFADirectArg(const ArgInfo &Info, const ABIType *Base,
+                               uint64_t Members, llvm::MaybeAlign DirectAlign) 
{
+  EXPECT_TRUE(Info.isDirect());
+  const llvm::abi::ArrayType *AT =
+      llvm::dyn_cast<llvm::abi::ArrayType>(Info.getCoerceToType());
+  ASSERT_NE(AT, nullptr);
+  EXPECT_EQ(AT->getElementType(), Base);
+  EXPECT_EQ(AT->getNumElements(), Members);
+  EXPECT_EQ(Info.getDirectOffset(), 0u);
+  EXPECT_EQ(Info.getDirectAlign(), DirectAlign);
+}
+
+// Records that cannot be passed in registers (e.g. non-trivial C++ types) are
+// classified as Indirect with ByVal=false under all AArch64 ABI kinds.
+TEST_F(AArch64TargetInfoTest, ClassifyArgumentRecordCannotPassInRegisters) {
+  // A record without CanPassInRegisters is treated like a C++ type with a
+  // non-trivial copy constructor or destructor.
+  const ABIType *CannotPass = TB.getRecordType(
+      {llvm::abi::FieldInfo(I32)}, llvm::TypeSize::getFixed(32), 
llvm::Align(4),
+      /*UnadjustedAlign=*/llvm::Align(4), llvm::abi::StructPacking::Default,
+      /*BaseClasses=*/{},
+      /*VirtualBaseClasses=*/{}, llvm::abi::RecordFlags::IsCXXRecord);
+
+  for (AArch64ABIKind Kind :
+       {AArch64ABIKind::AAPCS, AArch64ABIKind::DarwinPCS, 
AArch64ABIKind::Win64,
+        AArch64ABIKind::AAPCSSoft}) {
+    std::unique_ptr<TargetInfo> TI =
+        createAArch64TargetInfo(TB, AArch64ABIOptions(Kind));
+    std::unique_ptr<FunctionInfo> FI =
+        FunctionInfo::create(llvm::CallingConv::C, Void, {CannotPass});
+    TI->computeInfo(*FI);
+    expectNaturalAlignIndirect(FI->getArgInfo(0).Info, llvm::Align(4),
+                               /*ByVal=*/false);
+  }
+}
+
 // Transparent unions are classified as their first field type.
 TEST_F(AArch64TargetInfoTest, ClassifyArgumentTransparentUnion) {
   using llvm::abi::FieldInfo;
@@ -409,7 +453,8 @@ TEST_F(AArch64TargetInfoTest, 
ClassifyArgumentTransparentUnion) {
   // First field is i32; second field is ignored for classification.
   const ABIType *TUInt = TB.getUnionType(
       {FieldInfo(I32), FieldInfo(F32)}, llvm::TypeSize::getFixed(32),
-      llvm::Align(4), StructPacking::Default, RecordFlags::IsTransparent);
+      llvm::Align(4), /*UnadjustedAlign=*/llvm::Align(4),
+      StructPacking::Default, RecordFlags::IsTransparent);
 
   for (AArch64ABIKind Kind :
        {AArch64ABIKind::AAPCS, AArch64ABIKind::DarwinPCS, 
AArch64ABIKind::Win64,
@@ -425,7 +470,8 @@ TEST_F(AArch64TargetInfoTest, 
ClassifyArgumentTransparentUnion) {
   // First field is a promotable integer: DarwinPCS extends; others are Direct.
   const ABIType *TUChar = TB.getUnionType(
       {FieldInfo(I8), FieldInfo(U8)}, llvm::TypeSize::getFixed(8),
-      llvm::Align(1), StructPacking::Default, RecordFlags::IsTransparent);
+      llvm::Align(1), /*UnadjustedAlign=*/llvm::Align(1),
+      StructPacking::Default, RecordFlags::IsTransparent);
 
   {
     std::unique_ptr<TargetInfo> TI = createAArch64TargetInfo(
@@ -447,101 +493,85 @@ TEST_F(AArch64TargetInfoTest, 
ClassifyArgumentTransparentUnion) {
   }
 }
 
-static void expectNaturalAlignIndirect(const ArgInfo &Info,
-                                       llvm::Align ExpectedAlign, bool ByVal) {
-  EXPECT_TRUE(Info.isIndirect());
-  EXPECT_EQ(Info.getIndirectAlign(), ExpectedAlign);
-  EXPECT_EQ(Info.getIndirectByVal(), ByVal);
-}
-
-// Records that cannot be passed in registers (e.g. non-trivial C++ types) are
-// classified as Indirect with ByVal=false under all AArch64 ABI kinds.
-TEST_F(AArch64TargetInfoTest, ClassifyArgumentRecordCannotPassInRegisters) {
-  // A record without CanPassInRegisters is treated like a C++ type with a
-  // non-trivial copy constructor or destructor.
-  const ABIType *CannotPass = TB.getRecordType(
-      {llvm::abi::FieldInfo(I32)}, llvm::TypeSize::getFixed(32), 
llvm::Align(4),
-      llvm::abi::StructPacking::Default, /*BaseClasses=*/{},
-      /*VirtualBaseClasses=*/{}, llvm::abi::RecordFlags::IsCXXRecord);
-
-  for (AArch64ABIKind Kind :
-       {AArch64ABIKind::AAPCS, AArch64ABIKind::DarwinPCS, 
AArch64ABIKind::Win64,
-        AArch64ABIKind::AAPCSSoft}) {
-    std::unique_ptr<TargetInfo> TI =
-        createAArch64TargetInfo(TB, AArch64ABIOptions(Kind));
-    std::unique_ptr<FunctionInfo> FI =
-        FunctionInfo::create(llvm::CallingConv::C, Void, {CannotPass});
-    TI->computeInfo(*FI);
-    expectNaturalAlignIndirect(FI->getArgInfo(0).Info, llvm::Align(4),
-                               /*ByVal=*/false);
-  }
-}
-
 // Homogeneous floating-point aggregates of at most four members are returned
-// directly under AAPCS and DarwinPCS.
+// directly under AAPCS, DarwinPCS, and Win64.
 TEST_F(AArch64TargetInfoTest, ClassifyReturnHFADirect) {
   RecordFlags CXXFlags = passableRecordFlags(/*IsCXX=*/true);
 
   const ABIType *HFA2f =
-      makeRecord({FieldInfo(F32, 0), FieldInfo(F32, 32)}, 64, llvm::Align(4));
+      makeRecord({FieldInfo(F32, 0), FieldInfo(F32, 32)}, 64, llvm::Align(4),
+                 /*UnadjustedAlign=*/llvm::Align(4));
   const ABIType *HFA4d = makeRecord({FieldInfo(F64, 0), FieldInfo(F64, 64),
                                      FieldInfo(F64, 128), FieldInfo(F64, 192)},
-                                    256, llvm::Align(8));
+                                    256, llvm::Align(8),
+                                    /*UnadjustedAlign=*/llvm::Align(8));
   const ABIType *HFA3arr =
       makeRecord({FieldInfo(TB.getArrayType(F32, 3, /*SizeInBits=*/96), 0)}, 
96,
-                 llvm::Align(4));
+                 llvm::Align(4), /*UnadjustedAlign=*/llvm::Align(4));
   const ABIType *HFA2h =
-      makeRecord({FieldInfo(F16, 0), FieldInfo(F16, 16)}, 32, llvm::Align(2));
+      makeRecord({FieldInfo(F16, 0), FieldInfo(F16, 16)}, 32, llvm::Align(2),
+                 /*UnadjustedAlign=*/llvm::Align(2));
   const ABIType *HFANested =
-      makeRecord({FieldInfo(HFA2f, 0), FieldInfo(F32, 64)}, 96, 
llvm::Align(4));
+      makeRecord({FieldInfo(HFA2f, 0), FieldInfo(F32, 64)}, 96, llvm::Align(4),
+                 /*UnadjustedAlign=*/llvm::Align(4));
   const ABIType *HFAZeroBF =
       makeRecord({FieldInfo(I32, 0, /*IsBitField=*/true, /*BitFieldWidth=*/0),
                   FieldInfo(F32, 0), FieldInfo(F32, 32)},
-                 64, llvm::Align(4));
+                 64, llvm::Align(4), /*UnadjustedAlign=*/llvm::Align(4));
   const ABIType *HFAUnion = TB.getUnionType(
       {FieldInfo(F32, 0),
        FieldInfo(TB.getArrayType(F32, 3, /*SizeInBits=*/96), 0)},
-      llvm::TypeSize::getFixed(96), llvm::Align(4), StructPacking::Default,
+      llvm::TypeSize::getFixed(96), llvm::Align(4),
+      /*UnadjustedAlign=*/llvm::Align(4), StructPacking::Default,
       RecordFlags::CanPassInRegisters);
 
   // Short-vector aggregates (HVAs) follow the same rules.
-  const ABIType *HVA2x64 = makeRecord(
-      {FieldInfo(V2F32, 0), FieldInfo(V2F32, 64)}, 128, llvm::Align(8));
-  const ABIType *HVA2x128 = makeRecord(
-      {FieldInfo(V4F32, 0), FieldInfo(V4F32, 128)}, 256, llvm::Align(16));
+  const ABIType *HVA2x64 =
+      makeRecord({FieldInfo(V2F32, 0), FieldInfo(V2F32, 64)}, 128,
+                 llvm::Align(8), /*UnadjustedAlign=*/llvm::Align(8));
+  const ABIType *HVA2x128 =
+      makeRecord({FieldInfo(V4F32, 0), FieldInfo(V4F32, 128)}, 256,
+                 llvm::Align(16), /*UnadjustedAlign=*/llvm::Align(16));
 
   // 3 x float has 96 bits of payload; Clang's type size is 128, so it is an
   // HVA base like a 128-bit short vector.
   const ABIType *V3F32 =
       TB.getVectorType(F32, llvm::ElementCount::getFixed(3), llvm::Align(16));
   const ABIType *HVA3x32 =
-      makeRecord({FieldInfo(V3F32, 0)}, 128, llvm::Align(16));
+      makeRecord({FieldInfo(V3F32, 0)}, 128, llvm::Align(16),
+                 /*UnadjustedAlign=*/llvm::Align(16));
   const ABIType *HVA2xV3F32 = makeRecord(
-      {FieldInfo(V3F32, 0), FieldInfo(V3F32, 128)}, 256, llvm::Align(16));
+      {FieldInfo(V3F32, 0), FieldInfo(V3F32, 128)}, 256, llvm::Align(16),
+      /*UnadjustedAlign=*/llvm::Align(16));
 
   // A 2x2 float matrix is four homogeneous float members.
   const ABIType *HFAMatrix =
-      makeRecord({FieldInfo(Matrix, 0)}, 128, llvm::Align(4));
+      makeRecord({FieldInfo(Matrix, 0)}, 128, llvm::Align(4),
+                 /*UnadjustedAlign=*/llvm::Align(4));
   const ABIType *M2x1 = TB.getArrayType(F32, /*NumElements=*/2,
                                         /*SizeInBits=*/64,
                                         /*IsMatrixType=*/true);
   const ABIType *HFAMatrix2 =
-      makeRecord({FieldInfo(M2x1, 0)}, 64, llvm::Align(4));
+      makeRecord({FieldInfo(M2x1, 0)}, 64, llvm::Align(4),
+                 /*UnadjustedAlign=*/llvm::Align(4));
 
   // C++ records: empty bases are skipped and non-empty bases contribute
   // members.
-  const ABIType *EmptyRecord = makeRecord({}, 0, llvm::Align(1), CXXFlags);
+  const ABIType *EmptyRecord = makeRecord(
+      {}, 0, llvm::Align(1), /*UnadjustedAlign=*/llvm::Align(1), CXXFlags);
   const ABIType *HFAEmptyBase =
       makeRecord({FieldInfo(F32, 0), FieldInfo(F32, 32)}, 64, llvm::Align(4),
-                 CXXFlags, {FieldInfo(EmptyRecord, 0)});
+                 /*UnadjustedAlign=*/llvm::Align(4), CXXFlags,
+                 {FieldInfo(EmptyRecord, 0)});
   const ABIType *FloatBase =
-      makeRecord({FieldInfo(F32, 0)}, 32, llvm::Align(4), CXXFlags);
-  const ABIType *HFADerived =
-      makeRecord({FieldInfo(F32, 32)}, 64, llvm::Align(4), CXXFlags,
-                 {FieldInfo(FloatBase, 0)});
-
-  for (AArch64ABIKind Kind :
-       {AArch64ABIKind::AAPCS, AArch64ABIKind::DarwinPCS}) {
+      makeRecord({FieldInfo(F32, 0)}, 32, llvm::Align(4),
+                 /*UnadjustedAlign=*/llvm::Align(4), CXXFlags);
+  const ABIType *HFADerived = makeRecord(
+      {FieldInfo(F32, 32)}, 64, llvm::Align(4),
+      /*UnadjustedAlign=*/llvm::Align(4), CXXFlags, {FieldInfo(FloatBase, 0)});
+
+  for (AArch64ABIKind Kind : {AArch64ABIKind::AAPCS, AArch64ABIKind::DarwinPCS,
+                              AArch64ABIKind::Win64}) {
     std::unique_ptr<TargetInfo> TI =
         createAArch64TargetInfo(TB, AArch64ABIOptions(Kind));
     for (const ABIType *RetTy :
@@ -557,6 +587,106 @@ TEST_F(AArch64TargetInfoTest, ClassifyReturnHFADirect) {
   }
 }
 
+// Homogeneous floating-point and short-vector aggregates are passed as a
+// coerced array of the base type. AAPCS overrides stack alignment; DarwinPCS
+// and Win64 do not.
+TEST_F(AArch64TargetInfoTest, ClassifyArgumentHFADirect) {
+  RecordFlags CXXFlags = passableRecordFlags(/*IsCXX=*/true);
+
+  const ABIType *HFA2f =
+      makeRecord({FieldInfo(F32, 0), FieldInfo(F32, 32)}, 64, llvm::Align(4),
+                 /*UnadjustedAlign=*/llvm::Align(4));
+  const ABIType *HFA4d = makeRecord({FieldInfo(F64, 0), FieldInfo(F64, 64),
+                                     FieldInfo(F64, 128), FieldInfo(F64, 192)},
+                                    256, llvm::Align(8),
+                                    /*UnadjustedAlign=*/llvm::Align(8));
+  const ABIType *HFA3arr =
+      makeRecord({FieldInfo(TB.getArrayType(F32, 3, /*SizeInBits=*/96), 0)}, 
96,
+                 llvm::Align(4), /*UnadjustedAlign=*/llvm::Align(4));
+  const ABIType *HFA2h =
+      makeRecord({FieldInfo(F16, 0), FieldInfo(F16, 16)}, 32, llvm::Align(2),
+                 /*UnadjustedAlign=*/llvm::Align(2));
+  const ABIType *HFANested =
+      makeRecord({FieldInfo(HFA2f, 0), FieldInfo(F32, 64)}, 96, llvm::Align(4),
+                 /*UnadjustedAlign=*/llvm::Align(4));
+  const ABIType *HFAZeroBF =
+      makeRecord({FieldInfo(I32, 0, /*IsBitField=*/true, /*BitFieldWidth=*/0),
+                  FieldInfo(F32, 0), FieldInfo(F32, 32)},
+                 64, llvm::Align(4), /*UnadjustedAlign=*/llvm::Align(4));
+  const ABIType *HFAUnion = TB.getUnionType(
+      {FieldInfo(F32, 0),
+       FieldInfo(TB.getArrayType(F32, 3, /*SizeInBits=*/96), 0)},
+      llvm::TypeSize::getFixed(96), llvm::Align(4),
+      /*UnadjustedAlign=*/llvm::Align(4), StructPacking::Default,
+      RecordFlags::CanPassInRegisters);
+  const ABIType *HVA2x64 =
+      makeRecord({FieldInfo(V2F32, 0), FieldInfo(V2F32, 64)}, 128,
+                 llvm::Align(8), /*UnadjustedAlign=*/llvm::Align(8));
+  const ABIType *HVA2x128 =
+      makeRecord({FieldInfo(V4F32, 0), FieldInfo(V4F32, 128)}, 256,
+                 llvm::Align(16), /*UnadjustedAlign=*/llvm::Align(16));
+
+  const ABIType *EmptyRecord = makeRecord(
+      {}, 0, llvm::Align(1), /*UnadjustedAlign=*/llvm::Align(1), CXXFlags);
+  const ABIType *HFAEmptyBase =
+      makeRecord({FieldInfo(F32, 0), FieldInfo(F32, 32)}, 64, llvm::Align(4),
+                 /*UnadjustedAlign=*/llvm::Align(4), CXXFlags,
+                 {FieldInfo(EmptyRecord, 0)});
+  const ABIType *FloatBase =
+      makeRecord({FieldInfo(F32, 0)}, 32, llvm::Align(4),
+                 /*UnadjustedAlign=*/llvm::Align(4), CXXFlags);
+  const ABIType *HFADerived = makeRecord(
+      {FieldInfo(F32, 32)}, 64, llvm::Align(4),
+      /*UnadjustedAlign=*/llvm::Align(4), CXXFlags, {FieldInfo(FloatBase, 0)});
+  FieldInfo VirtualFloatBase(FloatBase, 0);
+
+  struct HFACase {
+    const ABIType *Ty;
+    const ABIType *Base;
+    uint64_t Members;
+    llvm::Align AAPCSAlign;
+  };
+  const HFACase Cases[] = {
+      {ComplexFloat, F32, 2, llvm::Align(8)},
+      {HFA2f, F32, 2, llvm::Align(8)},
+      {HFA4d, F64, 4, llvm::Align(8)},
+      {HFA3arr, F32, 3, llvm::Align(8)},
+      {HFA2h, F16, 2, llvm::Align(8)},
+      {HFANested, F32, 3, llvm::Align(8)},
+      {HFAZeroBF, F32, 2, llvm::Align(8)},
+      {HFAUnion, F32, 3, llvm::Align(8)},
+      {HVA2x64, V2F32, 2, llvm::Align(8)},
+      {HVA2x128, V4F32, 2, llvm::Align(16)},
+      {HFAEmptyBase, F32, 2, llvm::Align(8)},
+      {HFADerived, F32, 2, llvm::Align(8)},
+  };
+
+  {
+    std::unique_ptr<TargetInfo> TI =
+        createAArch64TargetInfo(TB, AArch64ABIOptions(AArch64ABIKind::AAPCS));
+    for (const HFACase &C : Cases) {
+      std::unique_ptr<FunctionInfo> FI =
+          FunctionInfo::create(llvm::CallingConv::C, Void, {C.Ty});
+      TI->computeInfo(*FI);
+      expectHFADirectArg(FI->getArgInfo(0).Info, C.Base, C.Members,
+                         C.AAPCSAlign);
+    }
+  }
+
+  for (AArch64ABIKind Kind :
+       {AArch64ABIKind::DarwinPCS, AArch64ABIKind::Win64}) {
+    std::unique_ptr<TargetInfo> TI =
+        createAArch64TargetInfo(TB, AArch64ABIOptions(Kind));
+    for (const HFACase &C : Cases) {
+      std::unique_ptr<FunctionInfo> FI =
+          FunctionInfo::create(llvm::CallingConv::C, Void, {C.Ty});
+      TI->computeInfo(*FI);
+      expectHFADirectArg(FI->getArgInfo(0).Info, C.Base, C.Members,
+                         /*DirectAlign=*/std::nullopt);
+    }
+  }
+}
+
 // Records that cannot pass in registers are returned indirectly before HFA
 // classification.
 TEST_F(AArch64TargetInfoTest, ClassifyReturnCXXCannotPassInRegistersIndirect) {
@@ -565,7 +695,7 @@ TEST_F(AArch64TargetInfoTest, 
ClassifyReturnCXXCannotPassInRegistersIndirect) {
 
   const ABIType *NonPassableHFA =
       makeRecord({FieldInfo(F32, 0), FieldInfo(F32, 32)}, 64, llvm::Align(4),
-                 RecordFlags::IsCXXRecord);
+                 /*UnadjustedAlign=*/llvm::Align(4), RecordFlags::IsCXXRecord);
 
   // struct FloatBase { float f; };
   // struct VirtualDerived : virtual FloatBase {};
@@ -574,9 +704,11 @@ TEST_F(AArch64TargetInfoTest, 
ClassifyReturnCXXCannotPassInRegistersIndirect) {
   // otherwise supply a homogeneous float member. The vbase pointer at offset 0
   // places the FloatBase subobject at offset 8, giving sizeof == 16.
   const ABIType *FloatBase = makeRecord({FieldInfo(F32, 0)}, 32, 
llvm::Align(4),
+                                        /*UnadjustedAlign=*/llvm::Align(4),
                                         passableRecordFlags(/*IsCXX=*/true));
   const ABIType *VirtualDerived =
-      makeRecord({}, 128, llvm::Align(8), RecordFlags::IsCXXRecord,
+      makeRecord({}, 128, llvm::Align(8), /*UnadjustedAlign=*/llvm::Align(8),
+                 RecordFlags::IsCXXRecord,
                  /*Bases=*/{}, /*VBases=*/{FieldInfo(FloatBase, 64)});
 
   const struct {
@@ -595,4 +727,71 @@ TEST_F(AArch64TargetInfoTest, 
ClassifyReturnCXXCannotPassInRegistersIndirect) {
   }
 }
 
+// AAPCS HFA stack alignment uses unadjusted alignment. A record-level aligned
+// attribute raises getAlignment() but must not change the 8/16 stack cap.
+TEST_F(AArch64TargetInfoTest, ClassifyArgumentOveralignedHFAAlign) {
+  // Two doubles already occupy 16 bytes, so aligned(16) does not add padding
+  // and the type remains an HFA. Unadjusted alignment stays 8.
+  const ABIType *RecordAlignedHFA =
+      makeRecord({FieldInfo(F64, 0), FieldInfo(F64, 64)}, 128, llvm::Align(16),
+                 /*UnadjustedAlign=*/llvm::Align(8));
+
+  // Four doubles occupy 32 bytes, so aligned(32) also remains an HFA.
+  // Unadjusted alignment is still 8, so AAPCS must not cap up to 16.
+  const ABIType *RecordAligned32HFA =
+      makeRecord({FieldInfo(F64, 0), FieldInfo(F64, 64), FieldInfo(F64, 128),
+                  FieldInfo(F64, 192)},
+                 256, llvm::Align(32), /*UnadjustedAlign=*/llvm::Align(8));
+
+  // Field-driven alignment of 16 is visible in unadjusted alignment, so AAPCS
+  // uses the 16-byte cap.
+  const ABIType *FieldAlignedHFA =
+      makeRecord({FieldInfo(F64, 0), FieldInfo(F64, 64)}, 128, llvm::Align(16),
+                 /*UnadjustedAlign=*/llvm::Align(16));
+
+  // Field-driven alignment of 32 is capped at 16.
+  const ABIType *FieldAligned32HFA =
+      makeRecord({FieldInfo(F64, 0), FieldInfo(F64, 64), FieldInfo(F64, 128),
+                  FieldInfo(F64, 192)},
+                 256, llvm::Align(32), /*UnadjustedAlign=*/llvm::Align(32));
+
+  {
+    std::unique_ptr<TargetInfo> TI =
+        createAArch64TargetInfo(TB, AArch64ABIOptions(AArch64ABIKind::AAPCS));
+
+    std::unique_ptr<FunctionInfo> FI =
+        FunctionInfo::create(llvm::CallingConv::C, Void, {RecordAlignedHFA});
+    TI->computeInfo(*FI);
+    expectHFADirectArg(FI->getArgInfo(0).Info, F64, 2, llvm::Align(8));
+
+    FI = FunctionInfo::create(llvm::CallingConv::C, Void, 
{RecordAligned32HFA});
+    TI->computeInfo(*FI);
+    expectHFADirectArg(FI->getArgInfo(0).Info, F64, 4, llvm::Align(8));
+
+    FI = FunctionInfo::create(llvm::CallingConv::C, Void, {FieldAlignedHFA});
+    TI->computeInfo(*FI);
+    expectHFADirectArg(FI->getArgInfo(0).Info, F64, 2, llvm::Align(16));
+
+    FI = FunctionInfo::create(llvm::CallingConv::C, Void, {FieldAligned32HFA});
+    TI->computeInfo(*FI);
+    expectHFADirectArg(FI->getArgInfo(0).Info, F64, 4, llvm::Align(16));
+  }
+
+  // DarwinPCS and Win64 coerce HFAs to an array but do not set DirectAlign,
+  // even when the record is overaligned.
+  for (AArch64ABIKind Kind :
+       {AArch64ABIKind::DarwinPCS, AArch64ABIKind::Win64}) {
+    std::unique_ptr<TargetInfo> TI =
+        createAArch64TargetInfo(TB, AArch64ABIOptions(Kind));
+    for (const ABIType *ArgTy : {RecordAlignedHFA, RecordAligned32HFA,
+                                 FieldAlignedHFA, FieldAligned32HFA}) {
+      std::unique_ptr<FunctionInfo> FI =
+          FunctionInfo::create(llvm::CallingConv::C, Void, {ArgTy});
+      TI->computeInfo(*FI);
+      EXPECT_TRUE(FI->getArgInfo(0).Info.isDirect());
+      EXPECT_EQ(FI->getArgInfo(0).Info.getDirectAlign(), std::nullopt);
+    }
+  }
+}
+
 } // namespace
diff --git a/llvm/unittests/ABI/FunctionInfoTest.cpp 
b/llvm/unittests/ABI/FunctionInfoTest.cpp
index 04fafc1108f83..224faa846f57d 100644
--- a/llvm/unittests/ABI/FunctionInfoTest.cpp
+++ b/llvm/unittests/ABI/FunctionInfoTest.cpp
@@ -39,6 +39,7 @@ class FunctionInfoTest : public ::testing::Test {
         I64(TB.getIntegerType(64, llvm::Align(8), /*Signed=*/true)),
         TwoI64(TB.getRecordType({FieldInfo(I64, 0), FieldInfo(I64, 64)},
                                 llvm::TypeSize::getFixed(128), llvm::Align(8),
+                                /*UnadjustedAlign=*/llvm::Align(8),
                                 StructPacking::Default)) {}
 };
 
diff --git a/llvm/unittests/ABI/IRTypeMapperTest.cpp 
b/llvm/unittests/ABI/IRTypeMapperTest.cpp
index 3eb8df5557de3..f324aa049a471 100644
--- a/llvm/unittests/ABI/IRTypeMapperTest.cpp
+++ b/llvm/unittests/ABI/IRTypeMapperTest.cpp
@@ -112,7 +112,8 @@ TEST_F(IRTypeMapperTest, 
PaddedAtomicMapsToValueAndTailPadding) {
   const llvm::abi::RecordType *ThreeBytes = TB.getRecordType(
       {llvm::abi::FieldInfo(I8, 0), llvm::abi::FieldInfo(I8, 8),
        llvm::abi::FieldInfo(I8, 16)},
-      llvm::TypeSize::getFixed(24), llvm::Align(1));
+      llvm::TypeSize::getFixed(24), llvm::Align(1),
+      /*UnadjustedAlign=*/llvm::Align(1));
   const llvm::abi::AtomicType *Atomic =
       TB.getAtomicType(ThreeBytes, 32, llvm::Align(4));
 
diff --git a/llvm/unittests/ABI/TypesTest.cpp b/llvm/unittests/ABI/TypesTest.cpp
index 85858f9c390da..78ae0448c82dd 100644
--- a/llvm/unittests/ABI/TypesTest.cpp
+++ b/llvm/unittests/ABI/TypesTest.cpp
@@ -41,6 +41,7 @@ class ABITypesTest : public ::testing::Test {
                                llvm::ArrayRef<FieldInfo> VBases = {},
                                Align Alignment = Align(1)) {
     return TB.getRecordType(Fields, TypeSize::getFixed(SizeBits), Alignment,
+                            /*UnadjustedAlign=*/Alignment,
                             StructPacking::Default, Bases, VBases, Flags);
   }
 };
@@ -150,6 +151,7 @@ TEST_F(ABITypesTest, GenericVector) {
 
   EXPECT_EQ(V4I32->getVectorKind(), VectorKind::Generic);
   EXPECT_FALSE(V4I32->isSVEType());
+  EXPECT_FALSE(V4I32->isSVESizelessType());
   EXPECT_TRUE(V4I32->isFixedLength());
   EXPECT_FALSE(V4I32->isTuple());
   EXPECT_EQ(V4I32->getSizeInBits(), TypeSize::getFixed(128));
@@ -163,6 +165,7 @@ TEST_F(ABITypesTest, SVEDataVector) {
 
   EXPECT_TRUE(SVInt32->isSVEData());
   EXPECT_TRUE(SVInt32->isSVEType());
+  EXPECT_TRUE(SVInt32->isSVESizelessType());
   EXPECT_TRUE(SVInt32->isScalable());
   EXPECT_FALSE(SVInt32->isTuple());
   EXPECT_EQ(SVInt32->getSizeInBits(), TypeSize::getScalable(128));
@@ -177,6 +180,7 @@ TEST_F(ABITypesTest, SVEDataVectorTuple) {
   const TupleType *SVInt32x3 = TB.getTupleType(SVInt32, /*NumVectors=*/3);
 
   EXPECT_TRUE(SVInt32x3->isTuple());
+  EXPECT_TRUE(SVInt32x3->isSVESizelessType());
   EXPECT_EQ(SVInt32x3->getNumVectors(), 3u);
   EXPECT_EQ(SVInt32x3->getVectorType(), SVInt32);
   EXPECT_EQ(SVInt32x3->getAlignment(), Align(16));
@@ -195,6 +199,7 @@ TEST_F(ABITypesTest, SVEPredicateVector) {
 
   EXPECT_TRUE(SVBool->isSVEPredicate());
   EXPECT_FALSE(SVBool->isSVEData());
+  EXPECT_TRUE(SVBool->isSVESizelessType());
   EXPECT_TRUE(SVBool->isScalable());
   EXPECT_EQ(SVBool->getSizeInBits(), TypeSize::getScalable(16));
   EXPECT_EQ(SVBool->getAlignment(), Align(2));
@@ -205,6 +210,7 @@ TEST_F(ABITypesTest, SVECount) {
 
   EXPECT_TRUE(SVCount->isSVECount());
   EXPECT_TRUE(SVCount->isSVEType());
+  EXPECT_TRUE(SVCount->isSVESizelessType());
   EXPECT_FALSE(SVCount->isSVEPredicate());
   EXPECT_TRUE(SVCount->isScalable());
   EXPECT_FALSE(SVCount->isTuple());
@@ -220,4 +226,26 @@ TEST_F(ABITypesTest, ScalableVectorIsNotZeroSized) {
   EXPECT_FALSE(SVInt32->isZeroSize());
 }
 
+// Fixed-length SVE from arm_sve_vector_bits keeps the SVE kind but is not
+// a sizeless builtin type.
+TEST_F(ABITypesTest, FixedLengthSVEIsNotSizeless) {
+  const llvm::abi::Type *I32 = TB.getIntegerType(32, Align(4), 
/*Signed=*/true);
+  const VectorType *FixedInt32 = TB.getVectorType(
+      I32, ElementCount::getFixed(4), Align(16), VectorKind::SVEData);
+  const llvm::abi::Type *I8 = TB.getIntegerType(8, Align(1), /*Signed=*/false);
+  const VectorType *FixedBool = TB.getVectorType(
+      I8, ElementCount::getFixed(16), Align(2), VectorKind::SVEPredicate);
+
+  EXPECT_TRUE(FixedInt32->isSVEData());
+  EXPECT_FALSE(FixedInt32->isSVESizelessType());
+  EXPECT_TRUE(FixedBool->isSVEPredicate());
+  EXPECT_FALSE(FixedBool->isSVESizelessType());
+}
+
+TEST_F(ABITypesTest, NonSVETypesAreNotSizelessSVE) {
+  const llvm::abi::Type *I32 = TB.getIntegerType(32, Align(4), 
/*Signed=*/true);
+  EXPECT_FALSE(I32->isSVESizelessType());
+  EXPECT_FALSE(TB.getVoidType()->isSVESizelessType());
+}
+
 } // namespace
diff --git a/llvm/unittests/ABI/X86TargetInfoTest.cpp 
b/llvm/unittests/ABI/X86TargetInfoTest.cpp
index 0ee99d1f7a9e3..10e696ddd2edd 100644
--- a/llvm/unittests/ABI/X86TargetInfoTest.cpp
+++ b/llvm/unittests/ABI/X86TargetInfoTest.cpp
@@ -55,11 +55,13 @@ class X86TargetInfoTest : public ::testing::Test {
         F64(TB.getFloatType(llvm::APFloat::IEEEdouble(), llvm::Align(8))),
         Void(TB.getVoidType()),
         Empty(TB.getRecordType({}, llvm::TypeSize::getFixed(8), llvm::Align(1),
+                               /*UnadjustedAlign=*/llvm::Align(1),
                                StructPacking::Default, {}, {},
                                RecordFlags::CanPassInRegisters)),
-        EmptyOver(TB.getRecordType({}, llvm::TypeSize::getFixed(128),
-                                   llvm::Align(16), StructPacking::Default, {},
-                                   {}, RecordFlags::CanPassInRegisters)) {}
+        EmptyOver(TB.getRecordType(
+            {}, llvm::TypeSize::getFixed(128), llvm::Align(16),
+            /*UnadjustedAlign=*/llvm::Align(16), StructPacking::Default, {}, 
{},
+            RecordFlags::CanPassInRegisters)) {}
 
   std::unique_ptr<TargetInfo> target() const {
     return createX86_64TargetInfo(const_cast<TypeBuilder &>(TB),
@@ -71,7 +73,7 @@ class X86TargetInfoTest : public ::testing::Test {
                          llvm::Align Alignment,
                          RecordFlags Flags = RecordFlags::None) {
     return TB.getUnionType(Fields, llvm::TypeSize::getFixed(SizeInBits),
-                           Alignment, StructPacking::Default,
+                           Alignment, Alignment, StructPacking::Default,
                            Flags | RecordFlags::CanPassInRegisters);
   }
 
@@ -141,7 +143,8 @@ TEST_F(X86TargetInfoTest, AtomicRecordIsIndirect) {
   std::unique_ptr<TargetInfo> TI;
   const ABIType *Value = TB.getRecordType(
       {FieldInfo(I32, 0)}, llvm::TypeSize::getFixed(32), llvm::Align(4),
-      StructPacking::Default, {}, {}, RecordFlags::CanPassInRegisters);
+      /*UnadjustedAlign=*/llvm::Align(4), StructPacking::Default, {}, {},
+      RecordFlags::CanPassInRegisters);
   const ABIType *Atomic = TB.getAtomicType(Value, 32, llvm::Align(4));
   const ArgInfo &Info = classifyArg(Atomic, FI, TI);
 
@@ -158,8 +161,9 @@ TEST_F(X86TargetInfoTest, RecordOfAtomicFloatsIsIndirect) {
   const ABIType *AtomicF32 = TB.getAtomicType(F32, 32, llvm::Align(4));
   const ABIType *Record = TB.getRecordType(
       {FieldInfo(AtomicF32, 0), FieldInfo(AtomicF32, 32)},
-      llvm::TypeSize::getFixed(64), llvm::Align(4), StructPacking::Default, {},
-      {}, RecordFlags::CanPassInRegisters);
+      llvm::TypeSize::getFixed(64), llvm::Align(4),
+      /*UnadjustedAlign=*/llvm::Align(4), StructPacking::Default, {}, {},
+      RecordFlags::CanPassInRegisters);
   const ArgInfo &Info = classifyArg(Record, FI, TI);
 
   ASSERT_TRUE(Info.isIndirect());
@@ -173,8 +177,8 @@ TEST_F(X86TargetInfoTest, RecordOfFloatsIsDirectSSE) {
   std::unique_ptr<TargetInfo> TI;
   const ABIType *Record = TB.getRecordType(
       {FieldInfo(F32, 0), FieldInfo(F32, 32)}, llvm::TypeSize::getFixed(64),
-      llvm::Align(4), StructPacking::Default, {}, {},
-      RecordFlags::CanPassInRegisters);
+      llvm::Align(4), /*UnadjustedAlign=*/llvm::Align(4),
+      StructPacking::Default, {}, {}, RecordFlags::CanPassInRegisters);
   const ArgInfo &Info = classifyArg(Record, FI, TI);
 
   ASSERT_TRUE(Info.isDirect());
@@ -255,7 +259,7 @@ TEST_F(X86TargetInfoTest, 
UnionWithEmptyMemberKeepsFloatPair) {
   std::unique_ptr<TargetInfo> TI;
   const ABIType *Floats = TB.getRecordType(
       {FieldInfo(F32, 0), FieldInfo(F32, 32)}, llvm::TypeSize::getFixed(64),
-      llvm::Align(4), StructPacking::Default, {}, {},
+      llvm::Align(4), llvm::Align(4), StructPacking::Default, {}, {},
       RecordFlags::CanPassInRegisters);
   const ABIType *U =
       unionOf({FieldInfo(Empty), FieldInfo(Floats)}, 64, llvm::Align(4));

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to