Author: Andy Kaylor Date: 2026-09-16T09:55:39-07:00 New Revision: 9df3e81cb2eefa2559eb9989bfdbd475dc23d0a2
URL: https://github.com/llvm/llvm-project/commit/9df3e81cb2eefa2559eb9989bfdbd475dc23d0a2 DIFF: https://github.com/llvm/llvm-project/commit/9df3e81cb2eefa2559eb9989bfdbd475dc23d0a2.diff LOG: [LLVMAABI][AARCH64] Handle homogeneous aggregate return types (#218799) This change implements the IsHomogenousAggregate() function, which is shared across targets, and uses it to implement direct passthrough of return values that meet the homogeneous aggregate criteria. I'm also adding a new AArch64ABIOptions object that will be used to track various conditions that are derived from target settings and must be accounted for in the ABI handling. This change adds only two options, IsILP32 and IsMicrosoftCXXABI. Other options are expected and will be added as they are needed. This change also revises the not-yet-implemented cases, having them return Ignore rather than Direct so that they fail in more obvious ways. This was necessary in order to be able to distinguish a return type that was classified as Direct because it was a homogeneous aggregate from one that was classified as Direct because it was an unhandled type. Assisted-by: Cursor / various models Added: clang/test/CodeGen/AArch64/abi-classify-return-types.cpp Modified: clang/lib/CodeGen/CodeGenModule.cpp clang/test/CodeGen/AArch64/abi-classify-return-types.c llvm/include/llvm/ABI/TargetInfo.h llvm/lib/ABI/TargetInfo.cpp llvm/lib/ABI/Targets/AArch64.cpp llvm/lib/ABI/Targets/BPF.cpp llvm/lib/ABI/Targets/X86.cpp llvm/unittests/ABI/AArch64TargetInfoTest.cpp Removed: ################################################################################ diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index ed69ef3e71135..7bb0e9d8ff560 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -397,15 +397,21 @@ CodeGenModule::getLLVMABITargetInfo(llvm::abi::TypeBuilder &TB) { case llvm::Triple::aarch64: case llvm::Triple::aarch64_32: case llvm::Triple::aarch64_be: { + llvm::abi::AArch64ABIOptions Opts; StringRef ABI = getTarget().getABI(); - llvm::abi::AArch64ABIKind Kind = llvm::abi::AArch64ABIKind::AAPCS; if (ABI == "darwinpcs") - Kind = llvm::abi::AArch64ABIKind::DarwinPCS; + Opts.Kind = llvm::abi::AArch64ABIKind::DarwinPCS; else if (T.isOSWindows()) - Kind = llvm::abi::AArch64ABIKind::Win64; + Opts.Kind = llvm::abi::AArch64ABIKind::Win64; else if (ABI == "aapcs-soft") - Kind = llvm::abi::AArch64ABIKind::AAPCSSoft; - TheLLVMABITargetInfo = llvm::abi::createAArch64TargetInfo(TB, Kind); + Opts.Kind = llvm::abi::AArch64ABIKind::AAPCSSoft; + else + Opts.Kind = llvm::abi::AArch64ABIKind::AAPCS; + + Opts.IsILP32 = T.getArch() == llvm::Triple::aarch64_32; + Opts.IsMicrosoftCXXABI = getTarget().getCXXABI().isMicrosoft(); + + TheLLVMABITargetInfo = llvm::abi::createAArch64TargetInfo(TB, Opts); return *TheLLVMABITargetInfo; } diff --git a/clang/test/CodeGen/AArch64/abi-classify-return-types.c b/clang/test/CodeGen/AArch64/abi-classify-return-types.c index 61b2332993087..066eec0950ec6 100644 --- a/clang/test/CodeGen/AArch64/abi-classify-return-types.c +++ b/clang/test/CodeGen/AArch64/abi-classify-return-types.c @@ -86,3 +86,82 @@ _BitInt(128) ret_bitint128(void) { return 0; } _BitInt(129) ret_bitint129(void) { return 0; } // CHECK: define{{.*}} void @ret_bitint129(ptr dead_on_unwind noalias writable sret(i256) align 16 %{{.*}}) + +// Homogeneous floating-point aggregates are returned directly. +_Complex float ret_complex_float() { return 1.0f; } +// CHECK: define{{.*}} { float, float } @ret_complex_float + +typedef struct { + float a, b; +} HFA2f; +HFA2f ret_hfa2f() { return (HFA2f){1.0f, 2.0f}; } +// CHECK: define{{.*}} %struct.HFA2f @ret_hfa2f + +typedef struct { + double a, b, c, d; +} HFA4d; +HFA4d ret_hfa4d() { return (HFA4d){1.0, 2.0, 3.0, 4.0}; } +// CHECK: define{{.*}} %struct.HFA4d @ret_hfa4d + +typedef struct { + float v[3]; +} HFA3arr; +HFA3arr ret_hfa3arr() { return (HFA3arr){{1.0f, 2.0f, 3.0f}}; } +// CHECK: define{{.*}} %struct.HFA3arr @ret_hfa3arr + +typedef struct { + _Float16 a, b; +} HFA2h; +HFA2h ret_hfa2h() { return (HFA2h){1.0f, 2.0f}; } +// CHECK: define{{.*}} %struct.HFA2h @ret_hfa2h + +// Short-vector aggregates (HVAs) follow the same rules. +typedef float v2f32_t __attribute__((vector_size(8))); +typedef struct { + v2f32_t a, b; +} HVA2x64; +HVA2x64 ret_hva2x64() { + HVA2x64 r; + return r; +} +// CHECK: define{{.*}} %struct.HVA2x64 @ret_hva2x64 + +typedef float v4f32_t __attribute__((vector_size(16))); +typedef struct { + v4f32_t a, b; +} HVA2x128; +HVA2x128 ret_hva2x128() { + HVA2x128 r; + return r; +} +// CHECK: define{{.*}} %struct.HVA2x128 @ret_hva2x128 + +typedef float v3f32_t __attribute__((vector_size(12))); +typedef struct { + v3f32_t a; +} HVA3x32; +HVA3x32 ret_hva3x32() { + HVA3x32 r; + return r; +} +// CHECK: define{{.*}} %struct.HVA3x32 @ret_hva3x32 + +// A 2x2 float matrix is four homogeneous float members. +typedef struct { + fx2x2_t m; +} HFAMatrix; +HFAMatrix ret_hfa_matrix() { + HFAMatrix r; + return r; +} +// CHECK: define{{.*}} %struct.HFAMatrix @ret_hfa_matrix + +typedef float fx2x1_t __attribute__((matrix_type(2, 1))); +typedef struct { + fx2x1_t m; +} HFAMatrix2; +HFAMatrix2 ret_hfa_matrix2() { + HFAMatrix2 r; + return r; +} +// CHECK: define{{.*}} %struct.HFAMatrix2 @ret_hfa_matrix2 diff --git a/clang/test/CodeGen/AArch64/abi-classify-return-types.cpp b/clang/test/CodeGen/AArch64/abi-classify-return-types.cpp new file mode 100644 index 0000000000000..185c471c94a21 --- /dev/null +++ b/clang/test/CodeGen/AArch64/abi-classify-return-types.cpp @@ -0,0 +1,78 @@ +// RUN: %clang_cc1 -triple arm64-apple-ios7.0 -target-abi darwinpcs -std=c++20 -emit-llvm -o - %s | FileCheck %s +// 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 --implicit-check-not="not yet implemented" +// RUN: %clang_cc1 -triple aarch64-linux-gnu -std=c++20 -emit-llvm -o - %s | FileCheck %s +// RUN: %clang_cc1 -triple aarch64-linux-gnu -std=c++20 -fexperimental-abi-lowering -emit-llvm -o - %s 2>&1 | FileCheck %s --implicit-check-not="not yet implemented" + +// Verify C++ homogeneous floating-point aggregate return classification matches +// between classic CodeGen and the LLVM ABI library. + +struct HFA2f { + float a, b; +}; +HFA2f ret_hfa2f() { return {}; } +// CHECK: define{{.*}} %struct.HFA2f @_Z9ret_hfa2fv() + +struct EmptyBase {}; +struct HFAEmptyBase : EmptyBase { + float a, b; +}; +HFAEmptyBase ret_hfa_empty_base() { return {}; } +// CHECK: define{{.*}} %struct.HFAEmptyBase @_Z18ret_hfa_empty_basev() + +struct EmptyBase1 {}; +struct EmptyBase2 {}; +struct HFAMultiEmptyBase : EmptyBase1, EmptyBase2 { + float a, b; +}; +HFAMultiEmptyBase ret_hfa_multi_empty_base() { return {}; } +// CHECK: define{{.*}} %struct.HFAMultiEmptyBase @_Z24ret_hfa_multi_empty_basev() + +struct HFABase { + float a; +}; +struct HFADerived : HFABase { + float b; +}; +HFADerived ret_hfa_derived() { return {}; } +// CHECK: define{{.*}} %struct.HFADerived @_Z15ret_hfa_derivedv() + +struct HFABaseAndFields : HFABase { + float b, c; +}; +HFABaseAndFields ret_hfa_base_fields() { return {}; } +// CHECK: define{{.*}} %struct.HFABaseAndFields @_Z19ret_hfa_base_fieldsv() + +struct FloatBase1 { + float a; +}; +struct FloatBase2 { + float b; +}; +struct HFATwoBases : FloatBase1, FloatBase2 {}; +HFATwoBases ret_hfa_two_bases() { return {}; } +// CHECK: define{{.*}} %struct.HFATwoBases @_Z17ret_hfa_two_basesv() + +struct HFANested { + HFA2f inner; + float c; +}; +HFANested ret_hfa_nested() { return {}; } +// CHECK: define{{.*}} %struct.HFANested @_Z14ret_hfa_nestedv() + +struct HFAZeroBF { + int : 0; + float a, b; +}; +HFAZeroBF ret_hfa_zerobf() { return {}; } +// CHECK: define{{.*}} %struct.HFAZeroBF @_Z14ret_hfa_zerobfv() + +struct Empty {}; +struct HFANoUniqueEmpty { + [[no_unique_address]] Empty e; + float a, b; +}; +HFANoUniqueEmpty ret_hfa_nua_empty() { return {}; } +// CHECK: define{{.*}} %struct.HFANoUniqueEmpty @_Z17ret_hfa_nua_emptyv() + +_Complex float ret_complex_float() { return 1.0f; } +// CHECK: define{{.*}} { float, float } @_Z17ret_complex_floatv() diff --git a/llvm/include/llvm/ABI/TargetInfo.h b/llvm/include/llvm/ABI/TargetInfo.h index ea621597e5b22..61f41992f2107 100644 --- a/llvm/include/llvm/ABI/TargetInfo.h +++ b/llvm/include/llvm/ABI/TargetInfo.h @@ -64,9 +64,13 @@ class TargetInfo { private: ABICompatInfo CompatInfo; +protected: + TypeBuilder &TB; + public: - TargetInfo() : CompatInfo() {} - explicit TargetInfo(const ABICompatInfo &Info) : CompatInfo(Info) {} + explicit TargetInfo(TypeBuilder &Builder) : CompatInfo(), TB(Builder) {} + TargetInfo(TypeBuilder &Builder, const ABICompatInfo &Info) + : CompatInfo(Info), TB(Builder) {} virtual ~TargetInfo() = default; @@ -90,6 +94,35 @@ class TargetInfo { /// Apply rules for classifying return types that are common to all targets. LLVM_ABI bool maybeCommonClassifyReturnType(FunctionInfo &FI) const; + + /// Return true if \p Ty is a valid base type for a homogeneous aggregate. + virtual bool isHomogeneousAggregateBaseType(const Type *Ty) const { + return false; + } + + /// Return true if a homogeneous aggregate with \p Members copies of \p Base + /// is small enough to be passed in registers for this ABI. + virtual bool isHomogeneousAggregateSmallEnough(const Type *Base, + uint64_t Members) const { + return false; + } + + /// Return true if zero-length bitfields should be ignored when deciding + /// whether an aggregate is homogeneous. + virtual bool isZeroLengthBitfieldPermittedInHomogeneousAggregate() const { + return false; + } + + /// Return true if the C++ ABI permits \p RT to be a homogeneous aggregate. + virtual bool isPermittedToBeHomogeneousAggregate(const RecordType *RT) const { + return true; + } + + /// Return true if \p Ty is an ELFv2-style homogeneous aggregate. \p Base is + /// set to the base element type and \p Members to the number of base + /// elements. + LLVM_ABI bool isHomogeneousAggregate(const Type *Ty, const Type *&Base, + uint64_t &Members) const; }; LLVM_ABI std::unique_ptr<TargetInfo> createBPFTargetInfo(TypeBuilder &TB); @@ -113,8 +146,20 @@ enum class AArch64ABIKind { AAPCSSoft, }; +/// Target / language flags that affect AArch64 ABI classification. +/// Callers (e.g. Clang) resolve Triple and LangOptions into these flags +/// rather than passing a Triple into the ABI library. +struct AArch64ABIOptions { + AArch64ABIKind Kind = AArch64ABIKind::AAPCS; + bool IsILP32 = false; + bool IsMicrosoftCXXABI = false; + + AArch64ABIOptions() = default; + explicit AArch64ABIOptions(AArch64ABIKind Kind) : Kind(Kind) {} +}; + LLVM_ABI std::unique_ptr<TargetInfo> -createAArch64TargetInfo(TypeBuilder &TB, AArch64ABIKind Kind); +createAArch64TargetInfo(TypeBuilder &TB, const AArch64ABIOptions &Opts); } // namespace abi } // namespace llvm diff --git a/llvm/lib/ABI/TargetInfo.cpp b/llvm/lib/ABI/TargetInfo.cpp index 507e3eb5bc120..9e8325eefc021 100644 --- a/llvm/lib/ABI/TargetInfo.cpp +++ b/llvm/lib/ABI/TargetInfo.cpp @@ -7,8 +7,12 @@ //===----------------------------------------------------------------------===// #include "llvm/ABI/TargetInfo.h" +#include "llvm/Support/Casting.h" +#include <algorithm> +#include <cstdint> using namespace llvm::abi; +using llvm::dyn_cast; bool TargetInfo::isAggregateTypeForABI(const Type *Ty) const { // Atomic values use the evaluation kind of their underlying value type. @@ -86,3 +90,117 @@ bool TargetInfo::maybeCommonClassifyReturnType(FunctionInfo &FI) const { return false; } + +namespace { + +bool isEmptyRecordForHA(const Type *Ty) { + const auto *RT = dyn_cast<RecordType>(Ty); + return RT && RT->isEmpty(); +} + +} // namespace + +bool TargetInfo::isHomogeneousAggregate(const Type *Ty, const Type *&Base, + uint64_t &Members) const { + // TODO: Add handling for Clang 23 compatibility with matrix types. + if (const auto *AT = dyn_cast<ArrayType>(Ty)) { + uint64_t NElements = AT->getNumElements(); + if (NElements == 0) + return false; + if (!isHomogeneousAggregate(AT->getElementType(), Base, Members)) + return false; + Members *= NElements; + } else if (const auto *RT = dyn_cast<RecordType>(Ty)) { + if (RT->hasFlexibleArrayMember()) + return false; + + Members = 0; + + // If this is a C++ record, check bases and ABI-specific restrictions. + if (RT->isCXXRecord()) { + if (!isPermittedToBeHomogeneousAggregate(RT)) + return false; + + for (const FieldInfo &BaseField : RT->getBaseClasses()) { + if (isEmptyRecordForHA(BaseField.FieldType)) + continue; + + uint64_t FldMembers = 0; + if (!isHomogeneousAggregate(BaseField.FieldType, Base, FldMembers)) + return false; + + Members += FldMembers; + } + } + + for (const FieldInfo &FD : RT->getFields()) { + // Ignore (non-zero arrays of) empty records. + const Type *FT = FD.FieldType; + while (const auto *AT = dyn_cast<ArrayType>(FT)) { + // Don't drill down to the element type of a matrix type here. + // That should fall through to the element isHomogeneousAggregate check. + if (AT->isMatrixType()) + break; + if (AT->getNumElements() == 0) + return false; + FT = AT->getElementType(); + } + if (isEmptyRecordForHA(FT)) + continue; + + if (isZeroLengthBitfieldPermittedInHomogeneousAggregate() && + FD.IsBitField && FD.BitFieldWidth == 0) + continue; + + uint64_t FldMembers = 0; + if (!isHomogeneousAggregate(FD.FieldType, Base, FldMembers)) + return false; + + Members = + RT->isUnion() ? std::max(Members, FldMembers) : Members + FldMembers; + } + + if (!Base) + return false; + + // Ensure there is no padding. + if (Base->getTypeAllocSize() * Members != Ty->getTypeAllocSize()) + return false; + } else { + Members = 1; + const Type *ElemTy = Ty; + if (const auto *CT = dyn_cast<ComplexType>(Ty)) { + Members = 2; + ElemTy = CT->getElementType(); + } + + // Most ABIs only support float, double, and some vector type widths. + if (!isHomogeneousAggregateBaseType(ElemTy)) + return false; + + // The base type must be the same for all members. Types that agree in both + // total size and mode (float vs. vector) are treated as equivalent here. + if (!Base) { + Base = ElemTy; + // If it's a non-power-of-2 vector, its ABI size is already a power-of-2, + // so widen it explicitly to match Clang. + if (const auto *VT = dyn_cast<VectorType>(Base)) { + assert(VT->isFixedLength() && + "scalable vectors are never homogeneous aggregates"); + uint64_t EltSize = + VT->getElementType()->getSizeInBits().getFixedValue(); + unsigned NumElements = + VT->getTypeAllocSize().getFixedValue() * 8 / EltSize; + if (NumElements != VT->getNumElements().getKnownMinValue()) + Base = TB.getVectorType(VT->getElementType(), + ElementCount::getFixed(NumElements), + VT->getAlignment()); + } + } + + if (Base->isVector() != ElemTy->isVector() || + Base->getTypeAllocSize() != ElemTy->getTypeAllocSize()) + return false; + } + return Members > 0 && isHomogeneousAggregateSmallEnough(Base, Members); +} diff --git a/llvm/lib/ABI/Targets/AArch64.cpp b/llvm/lib/ABI/Targets/AArch64.cpp index bf48f6a47dabf..9faea71ec9b51 100644 --- a/llvm/lib/ABI/Targets/AArch64.cpp +++ b/llvm/lib/ABI/Targets/AArch64.cpp @@ -9,16 +9,20 @@ #include "llvm/ABI/FunctionInfo.h" #include "llvm/ABI/TargetInfo.h" #include "llvm/ABI/Types.h" +#include "llvm/Support/Casting.h" #include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/MathExtras.h" #include "llvm/Support/WithColor.h" +#include <algorithm> +#include <cstdint> namespace llvm { namespace abi { class AArch64TargetInfo : public TargetInfo { public: - AArch64TargetInfo(TypeBuilder &TB, AArch64ABIKind Kind) - : TB(TB), Kind(Kind) {} + AArch64TargetInfo(TypeBuilder &TB, const AArch64ABIOptions &Opts) + : TargetInfo(TB), Opts(Opts) {} void computeInfo(FunctionInfo &FI) const override { if (!maybeCommonClassifyReturnType(FI)) @@ -37,21 +41,27 @@ class AArch64TargetInfo : public TargetInfo { } private: - [[maybe_unused]] TypeBuilder &TB; - AArch64ABIKind Kind; + AArch64ABIOptions Opts; ArgInfo classifyReturnType(const Type *RetTy, bool IsVariadicFn) const; ArgInfo classifyArgumentType(const Type *Ty, bool IsVariadicFn, bool IsNamedArg, unsigned CallingConvention, unsigned &NSRN, unsigned &NPRN) const; - bool isDarwinPCS() const { return Kind == AArch64ABIKind::DarwinPCS; } + bool isDarwinPCS() const { return Opts.Kind == AArch64ABIKind::DarwinPCS; } + bool isSoftFloat() const { return Opts.Kind == AArch64ABIKind::AAPCSSoft; } bool passAsAggregateType(const Type *Ty) const; + + bool isHomogeneousAggregateBaseType(const Type *Ty) const override; + bool isHomogeneousAggregateSmallEnough(const Type *Base, + uint64_t Members) const override; + bool isZeroLengthBitfieldPermittedInHomogeneousAggregate() const override; + bool isPermittedToBeHomogeneousAggregate(const RecordType *RT) const override; }; -std::unique_ptr<TargetInfo> createAArch64TargetInfo(TypeBuilder &TB, - AArch64ABIKind Kind) { - return std::make_unique<AArch64TargetInfo>(TB, Kind); +std::unique_ptr<TargetInfo> +createAArch64TargetInfo(TypeBuilder &TB, const AArch64ABIOptions &Opts) { + return std::make_unique<AArch64TargetInfo>(TB, Opts); } static void reportNYI(StringRef Feature) { @@ -67,7 +77,7 @@ ArgInfo AArch64TargetInfo::classifyReturnType(const Type *RetTy, if (RetTy->isVector()) { reportNYI("Vector return type handling"); - return ArgInfo::getDirect(); + return ArgInfo::getIgnore(); } if (!passAsAggregateType(RetTy)) { @@ -84,8 +94,18 @@ ArgInfo AArch64TargetInfo::classifyReturnType(const Type *RetTy, return ArgInfo::getDirect(); } + // TODO: Handle empty records and zero-size non-SVE types. + + const Type *Base = nullptr; + uint64_t Members = 0; + if (isHomogeneousAggregate(RetTy, Base, Members) && + !(Opts.IsILP32 && IsVariadicFn)) { + // Homogeneous Floating-point Aggregates (HFAs) are returned directly. + return ArgInfo::getDirect(); + } + reportNYI("Aggregate return type handling"); - return ArgInfo::getDirect(); + return ArgInfo::getIgnore(); } ArgInfo AArch64TargetInfo::classifyArgumentType( @@ -93,11 +113,9 @@ ArgInfo AArch64TargetInfo::classifyArgumentType( unsigned CallingConvention, unsigned &NSRN, unsigned &NPRN) const { Ty = useFirstFieldIfTransparentUnion(Ty); - // TODO: Handle variadic functins here when Windows Arm64 EC is supported. - if (Ty->isVector()) { reportNYI("Vector argument type handling"); - return ArgInfo::getDirect(); + return ArgInfo::getIgnore(); } if (!passAsAggregateType(Ty)) { @@ -127,7 +145,7 @@ ArgInfo AArch64TargetInfo::classifyArgumentType( } reportNYI("Aggregate argument type handling"); - return ArgInfo::getDirect(); + return ArgInfo::getIgnore(); } bool AArch64TargetInfo::passAsAggregateType(const Type *Ty) const { @@ -135,5 +153,54 @@ bool AArch64TargetInfo::passAsAggregateType(const Type *Ty) const { return isAggregateTypeForABI(Ty); } +bool AArch64TargetInfo::isHomogeneousAggregateBaseType(const Type *Ty) const { + // Soft-float ABI: no types are homogeneous aggregates. + if (isSoftFloat()) + return false; + + // Homogeneous aggregates for AAPCS64 must have base types of a floating + // point type or a short-vector type. + if (Ty->isFloat()) + return true; + + if (const auto *VT = dyn_cast<VectorType>(Ty)) { + if (VT->isScalable() || VT->isSVEData() || VT->isSVEPredicate()) + return false; + + // Clang's getTypeSize for non-power-of-2 vectors rounds the width up to + // the next power-of-2 alignment (e.g. 3 x float is 96 bits of payload but + // 128 bits of ABI size), so those vectors are short-vector HVA bases. + uint64_t VecSize = + bit_ceil(std::max<uint64_t>(8, VT->getSizeInBits().getFixedValue())); + if (VecSize == 64 || VecSize == 128) + return true; + } + return false; +} + +bool AArch64TargetInfo::isHomogeneousAggregateSmallEnough( + const Type * /*Base*/, uint64_t Members) const { + return Members <= 4; +} + +bool AArch64TargetInfo::isZeroLengthBitfieldPermittedInHomogeneousAggregate() + const { + // AAPCS64 applies homogeneity to the output of the data layout decision, so + // zero-length bitfields do not affect homogeneity. + return true; +} + +bool AArch64TargetInfo::isPermittedToBeHomogeneousAggregate( + const RecordType *RT) const { + if (Opts.IsMicrosoftCXXABI && RT->isCXXRecord()) { + // This won't always return false, but we don't have enough information to + // perform the full check correctly yet. + reportNYI("MicrosoftCXXABI homogeneous record classification"); + return false; + } + + return true; +} + } // namespace abi } // namespace llvm diff --git a/llvm/lib/ABI/Targets/BPF.cpp b/llvm/lib/ABI/Targets/BPF.cpp index 08127938d1747..70b838a5abd20 100644 --- a/llvm/lib/ABI/Targets/BPF.cpp +++ b/llvm/lib/ABI/Targets/BPF.cpp @@ -16,8 +16,6 @@ namespace llvm::abi { class BPFTargetInfo : public TargetInfo { private: - TypeBuilder &TB; - ArgInfo classifyReturnType(const Type *RetTy) const { if (RetTy->isVoid()) return ArgInfo::getIgnore(); @@ -72,7 +70,7 @@ class BPFTargetInfo : public TargetInfo { } public: - BPFTargetInfo(TypeBuilder &TB) : TB(TB) {} + BPFTargetInfo(TypeBuilder &Builder) : TargetInfo(Builder) {} void computeInfo(FunctionInfo &FI) const override { FI.getReturnInfo() = classifyReturnType(FI.getReturnType()); diff --git a/llvm/lib/ABI/Targets/X86.cpp b/llvm/lib/ABI/Targets/X86.cpp index 5c4e29652a765..08a5e20cb9070 100644 --- a/llvm/lib/ABI/Targets/X86.cpp +++ b/llvm/lib/ABI/Targets/X86.cpp @@ -53,9 +53,7 @@ static uint64_t getClangVectorWidthInBits(const VectorType *VT) { EltWidth = getClangIntegerWidthInBits(IT); uint64_t Width = std::max<uint64_t>(8, EltWidth * VT->getNumElements().getKnownMinValue()); - if (Width & (Width - 1)) - Width = llvm::alignTo(Width, llvm::bit_ceil(Width)); - return Width; + return llvm::bit_ceil(Width); } // The storage-container width of a type, mirroring Clang's getTypeSize. Used on @@ -74,7 +72,6 @@ class X86_64TargetInfo : public TargetInfo { enum Class { Integer, Sse, SseUp, X87, X87Up, ComplexX87, NoClass, Memory }; private: - TypeBuilder &TB; X86AVXABILevel AVXLevel; bool Has64BitPointers; @@ -115,7 +112,7 @@ class X86_64TargetInfo : public TargetInfo { public: X86_64TargetInfo(TypeBuilder &TypeBuilder, X86AVXABILevel AVXABILevel, bool Has64BitPtrs, const ABICompatInfo &Compat) - : TargetInfo(Compat), TB(TypeBuilder), AVXLevel(AVXABILevel), + : TargetInfo(TypeBuilder, Compat), AVXLevel(AVXABILevel), Has64BitPointers(Has64BitPtrs) {} bool has64BitPointers() const { return Has64BitPointers; } diff --git a/llvm/unittests/ABI/AArch64TargetInfoTest.cpp b/llvm/unittests/ABI/AArch64TargetInfoTest.cpp index 0da1c00d4c9f4..affa187ae7dad 100644 --- a/llvm/unittests/ABI/AArch64TargetInfoTest.cpp +++ b/llvm/unittests/ABI/AArch64TargetInfoTest.cpp @@ -13,18 +13,29 @@ #include "llvm/IR/CallingConv.h" #include "llvm/Support/Alignment.h" #include "llvm/Support/Allocator.h" +#include "llvm/Support/TypeSize.h" #include "gtest/gtest.h" +#include <cstdint> namespace { using ABIType = llvm::abi::Type; using llvm::abi::AArch64ABIKind; +using llvm::abi::AArch64ABIOptions; using llvm::abi::ArgInfo; using llvm::abi::createAArch64TargetInfo; +using llvm::abi::FieldInfo; using llvm::abi::FunctionInfo; +using llvm::abi::RecordFlags; +using llvm::abi::RequiredArgs; +using llvm::abi::StructPacking; using llvm::abi::TargetInfo; using llvm::abi::TypeBuilder; +static void expectUncoercedDirect(const ArgInfo &Info); +static void expectExtendInteger(const ArgInfo &Info, const ABIType *Ty, + bool IsSigned); + class AArch64TargetInfoTest : public ::testing::Test { protected: llvm::BumpPtrAllocator Alloc; @@ -38,6 +49,7 @@ class AArch64TargetInfoTest : public ::testing::Test { const ABIType *U32; const ABIType *I64; const ABIType *U64; + const ABIType *F16; const ABIType *F32; const ABIType *F64; const ABIType *Ptr; @@ -48,6 +60,9 @@ class AArch64TargetInfoTest : public ::testing::Test { const ABIType *BitInt65; const ABIType *BitInt128; const ABIType *BitInt129; + const ABIType *ComplexFloat; + const ABIType *V2F32; + const ABIType *V4F32; AArch64TargetInfoTest() : TB(Alloc), Bool(TB.getIntegerType(1, llvm::Align(1), /*Signed=*/false)), @@ -59,6 +74,7 @@ class AArch64TargetInfoTest : public ::testing::Test { U32(TB.getIntegerType(32, llvm::Align(4), /*Signed=*/false)), I64(TB.getIntegerType(64, llvm::Align(8), /*Signed=*/true)), U64(TB.getIntegerType(64, llvm::Align(8), /*Signed=*/false)), + F16(TB.getFloatType(llvm::APFloat::IEEEhalf(), llvm::Align(2))), F32(TB.getFloatType(llvm::APFloat::IEEEsingle(), llvm::Align(4))), F64(TB.getFloatType(llvm::APFloat::IEEEdouble(), llvm::Align(8))), Ptr(TB.getPointerType(64, llvm::Align(8))), Void(TB.getVoidType()), @@ -73,7 +89,28 @@ class AArch64TargetInfoTest : public ::testing::Test { BitInt128(TB.getIntegerType(128, llvm::Align(16), /*Signed=*/true, /*IsBitInt=*/true)), BitInt129(TB.getIntegerType(129, llvm::Align(16), /*Signed=*/true, - /*IsBitInt=*/true)) {} + /*IsBitInt=*/true)), + ComplexFloat(TB.getComplexType(F32, llvm::Align(4))), + V2F32(TB.getVectorType(F32, llvm::ElementCount::getFixed(2), + llvm::Align(8))), + V4F32(TB.getVectorType(F32, llvm::ElementCount::getFixed(4), + llvm::Align(16))) {} + + static RecordFlags passableRecordFlags(bool IsCXX = false) { + unsigned Flags = RecordFlags::CanPassInRegisters; + if (IsCXX) + Flags |= RecordFlags::IsCXXRecord; + return static_cast<RecordFlags>(Flags); + } + + const ABIType *makeRecord(llvm::ArrayRef<FieldInfo> Fields, uint64_t SizeBits, + llvm::Align Align, + 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); + } }; static void expectUncoercedDirect(const ArgInfo &Info) { @@ -97,7 +134,7 @@ static void expectAlignedIndirect(const ArgInfo &Info, llvm::Align Align, TEST_F(AArch64TargetInfoTest, ClassifyReturnVoidIsIgnore) { std::unique_ptr<TargetInfo> TI = - createAArch64TargetInfo(TB, AArch64ABIKind::DarwinPCS); + createAArch64TargetInfo(TB, AArch64ABIOptions(AArch64ABIKind::DarwinPCS)); std::unique_ptr<FunctionInfo> FI = FunctionInfo::create(llvm::CallingConv::C, Void, {}); @@ -112,10 +149,10 @@ TEST_F(AArch64TargetInfoTest, ClassifyReturnVoidIsIgnore) { // return path under AAPCS. TEST_F(AArch64TargetInfoTest, ClassifyReturnScalarsDirectAAPCS) { std::unique_ptr<TargetInfo> TI = - createAArch64TargetInfo(TB, AArch64ABIKind::AAPCS); + createAArch64TargetInfo(TB, AArch64ABIOptions(AArch64ABIKind::AAPCS)); - for (const ABIType *RetTy : - {Bool, I8, U8, I16, U16, I32, U32, I64, U64, F32, F64, Ptr, Matrix}) { + for (const ABIType *RetTy : {Bool, I8, U8, I16, U16, I32, U32, I64, U64, F16, + F32, F64, Ptr, Matrix}) { std::unique_ptr<FunctionInfo> FI = FunctionInfo::create(llvm::CallingConv::C, RetTy, {}); FI->getReturnInfo() = ArgInfo::getIgnore(); @@ -128,9 +165,10 @@ TEST_F(AArch64TargetInfoTest, ClassifyReturnScalarsDirectAAPCS) { // returns are extended. TEST_F(AArch64TargetInfoTest, ClassifyReturnScalarsDirectOrPromotableDarwin) { std::unique_ptr<TargetInfo> TI = - createAArch64TargetInfo(TB, AArch64ABIKind::DarwinPCS); + createAArch64TargetInfo(TB, AArch64ABIOptions(AArch64ABIKind::DarwinPCS)); - for (const ABIType *RetTy : {I32, U32, I64, U64, F32, F64, Ptr, Matrix}) { + for (const ABIType *RetTy : + {I32, U32, I64, U64, F16, F32, F64, Ptr, Matrix}) { std::unique_ptr<FunctionInfo> FI = FunctionInfo::create(llvm::CallingConv::C, RetTy, {}); FI->getReturnInfo() = ArgInfo::getIgnore(); @@ -153,10 +191,10 @@ TEST_F(AArch64TargetInfoTest, ClassifyReturnScalarsDirectOrPromotableDarwin) { // return path under AAPCSSoft. TEST_F(AArch64TargetInfoTest, ClassifyReturnScalarsDirectAAPCSSoft) { std::unique_ptr<TargetInfo> TI = - createAArch64TargetInfo(TB, AArch64ABIKind::AAPCSSoft); + createAArch64TargetInfo(TB, AArch64ABIOptions(AArch64ABIKind::AAPCSSoft)); - for (const ABIType *RetTy : - {Bool, I8, U8, I16, U16, I32, U32, I64, U64, F32, F64, Ptr, Matrix}) { + for (const ABIType *RetTy : {Bool, I8, U8, I16, U16, I32, U32, I64, U64, F16, + F32, F64, Ptr, Matrix}) { std::unique_ptr<FunctionInfo> FI = FunctionInfo::create(llvm::CallingConv::C, RetTy, {}); FI->getReturnInfo() = ArgInfo::getIgnore(); @@ -169,7 +207,7 @@ TEST_F(AArch64TargetInfoTest, ClassifyReturnScalarsDirectAAPCSSoft) { // Wider _BitInt types are returned indirectly. TEST_F(AArch64TargetInfoTest, ClassifyReturnBitIntAAPCS) { std::unique_ptr<TargetInfo> TI = - createAArch64TargetInfo(TB, AArch64ABIKind::AAPCS); + createAArch64TargetInfo(TB, AArch64ABIOptions(AArch64ABIKind::AAPCS)); for (const ABIType *RetTy : {BitInt7, UBitInt7, BitInt65, BitInt128}) { std::unique_ptr<FunctionInfo> FI = @@ -191,7 +229,7 @@ TEST_F(AArch64TargetInfoTest, ClassifyReturnBitIntAAPCS) { // indirectly. TEST_F(AArch64TargetInfoTest, ClassifyReturnBitIntDarwin) { std::unique_ptr<TargetInfo> TI = - createAArch64TargetInfo(TB, AArch64ABIKind::DarwinPCS); + createAArch64TargetInfo(TB, AArch64ABIOptions(AArch64ABIKind::DarwinPCS)); for (const ABIType *RetTy : {BitInt65, BitInt128}) { std::unique_ptr<FunctionInfo> FI = @@ -227,7 +265,7 @@ TEST_F(AArch64TargetInfoTest, ClassifyReturnBitIntDarwin) { // return path under Win64. TEST_F(AArch64TargetInfoTest, ClassifyReturnScalarsDirectWin64) { std::unique_ptr<TargetInfo> TI = - createAArch64TargetInfo(TB, AArch64ABIKind::Win64); + createAArch64TargetInfo(TB, AArch64ABIOptions(AArch64ABIKind::Win64)); for (const ABIType *RetTy : {Bool, I8, U8, I16, U16, I32, U32, I64, U64, F32, F64, Ptr, Matrix}) { @@ -243,10 +281,10 @@ TEST_F(AArch64TargetInfoTest, ClassifyReturnScalarsDirectWin64) { // argument path under AAPCS. TEST_F(AArch64TargetInfoTest, ClassifyArgumentScalarsDirectAAPCS) { std::unique_ptr<TargetInfo> TI = - createAArch64TargetInfo(TB, AArch64ABIKind::AAPCS); + createAArch64TargetInfo(TB, AArch64ABIOptions(AArch64ABIKind::AAPCS)); - for (const ABIType *ArgTy : - {Bool, I8, U8, I16, U16, I32, U32, I64, U64, F32, F64, Ptr, Matrix}) { + for (const ABIType *ArgTy : {Bool, I8, U8, I16, U16, I32, U32, I64, U64, F16, + F32, F64, Ptr, Matrix}) { std::unique_ptr<FunctionInfo> FI = FunctionInfo::create(llvm::CallingConv::C, Void, {ArgTy}); TI->computeInfo(*FI); @@ -258,9 +296,10 @@ TEST_F(AArch64TargetInfoTest, ClassifyArgumentScalarsDirectAAPCS) { // arguments are extended. TEST_F(AArch64TargetInfoTest, ClassifyArgumentScalarsDirectOrPromotableDarwin) { std::unique_ptr<TargetInfo> TI = - createAArch64TargetInfo(TB, AArch64ABIKind::DarwinPCS); + createAArch64TargetInfo(TB, AArch64ABIOptions(AArch64ABIKind::DarwinPCS)); - for (const ABIType *ArgTy : {I32, U32, I64, U64, F32, F64, Ptr, Matrix}) { + for (const ABIType *ArgTy : + {I32, U32, I64, U64, F16, F32, F64, Ptr, Matrix}) { std::unique_ptr<FunctionInfo> FI = FunctionInfo::create(llvm::CallingConv::C, Void, {ArgTy}); TI->computeInfo(*FI); @@ -281,10 +320,10 @@ TEST_F(AArch64TargetInfoTest, ClassifyArgumentScalarsDirectOrPromotableDarwin) { // argument path under AAPCSSoft. TEST_F(AArch64TargetInfoTest, ClassifyArgumentScalarsDirectAAPCSSoft) { std::unique_ptr<TargetInfo> TI = - createAArch64TargetInfo(TB, AArch64ABIKind::AAPCSSoft); + createAArch64TargetInfo(TB, AArch64ABIOptions(AArch64ABIKind::AAPCSSoft)); - for (const ABIType *ArgTy : - {Bool, I8, U8, I16, U16, I32, U32, I64, U64, F32, F64, Ptr, Matrix}) { + for (const ABIType *ArgTy : {Bool, I8, U8, I16, U16, I32, U32, I64, U64, F16, + F32, F64, Ptr, Matrix}) { std::unique_ptr<FunctionInfo> FI = FunctionInfo::create(llvm::CallingConv::C, Void, {ArgTy}); TI->computeInfo(*FI); @@ -296,7 +335,7 @@ TEST_F(AArch64TargetInfoTest, ClassifyArgumentScalarsDirectAAPCSSoft) { // Wider _BitInt types are passed indirectly without byval. TEST_F(AArch64TargetInfoTest, ClassifyArgumentBitIntAAPCS) { std::unique_ptr<TargetInfo> TI = - createAArch64TargetInfo(TB, AArch64ABIKind::AAPCS); + createAArch64TargetInfo(TB, AArch64ABIOptions(AArch64ABIKind::AAPCS)); for (const ABIType *ArgTy : {BitInt7, UBitInt7, BitInt65, BitInt128}) { std::unique_ptr<FunctionInfo> FI = @@ -317,7 +356,7 @@ TEST_F(AArch64TargetInfoTest, ClassifyArgumentBitIntAAPCS) { // indirectly without byval. TEST_F(AArch64TargetInfoTest, ClassifyArgumentBitIntDarwin) { std::unique_ptr<TargetInfo> TI = - createAArch64TargetInfo(TB, AArch64ABIKind::DarwinPCS); + createAArch64TargetInfo(TB, AArch64ABIOptions(AArch64ABIKind::DarwinPCS)); for (const ABIType *ArgTy : {BitInt65, BitInt128}) { std::unique_ptr<FunctionInfo> FI = @@ -350,7 +389,7 @@ TEST_F(AArch64TargetInfoTest, ClassifyArgumentBitIntDarwin) { // argument path under Win64. TEST_F(AArch64TargetInfoTest, ClassifyArgumentScalarsDirectWin64) { std::unique_ptr<TargetInfo> TI = - createAArch64TargetInfo(TB, AArch64ABIKind::Win64); + createAArch64TargetInfo(TB, AArch64ABIOptions(AArch64ABIKind::Win64)); for (const ABIType *ArgTy : {Bool, I8, U8, I16, U16, I32, U32, I64, U64, F32, F64, Ptr, Matrix}) { @@ -375,7 +414,8 @@ TEST_F(AArch64TargetInfoTest, ClassifyArgumentTransparentUnion) { for (AArch64ABIKind Kind : {AArch64ABIKind::AAPCS, AArch64ABIKind::DarwinPCS, AArch64ABIKind::Win64, AArch64ABIKind::AAPCSSoft}) { - std::unique_ptr<TargetInfo> TI = createAArch64TargetInfo(TB, Kind); + std::unique_ptr<TargetInfo> TI = + createAArch64TargetInfo(TB, AArch64ABIOptions(Kind)); std::unique_ptr<FunctionInfo> FI = FunctionInfo::create(llvm::CallingConv::C, Void, {TUInt}); TI->computeInfo(*FI); @@ -388,8 +428,8 @@ TEST_F(AArch64TargetInfoTest, ClassifyArgumentTransparentUnion) { llvm::Align(1), StructPacking::Default, RecordFlags::IsTransparent); { - std::unique_ptr<TargetInfo> TI = - createAArch64TargetInfo(TB, AArch64ABIKind::DarwinPCS); + std::unique_ptr<TargetInfo> TI = createAArch64TargetInfo( + TB, AArch64ABIOptions(AArch64ABIKind::DarwinPCS)); std::unique_ptr<FunctionInfo> FI = FunctionInfo::create(llvm::CallingConv::C, Void, {TUChar}); TI->computeInfo(*FI); @@ -398,7 +438,8 @@ TEST_F(AArch64TargetInfoTest, ClassifyArgumentTransparentUnion) { for (AArch64ABIKind Kind : {AArch64ABIKind::AAPCS, AArch64ABIKind::Win64, AArch64ABIKind::AAPCSSoft}) { - std::unique_ptr<TargetInfo> TI = createAArch64TargetInfo(TB, Kind); + std::unique_ptr<TargetInfo> TI = + createAArch64TargetInfo(TB, AArch64ABIOptions(Kind)); std::unique_ptr<FunctionInfo> FI = FunctionInfo::create(llvm::CallingConv::C, Void, {TUChar}); TI->computeInfo(*FI); @@ -426,7 +467,8 @@ TEST_F(AArch64TargetInfoTest, ClassifyArgumentRecordCannotPassInRegisters) { for (AArch64ABIKind Kind : {AArch64ABIKind::AAPCS, AArch64ABIKind::DarwinPCS, AArch64ABIKind::Win64, AArch64ABIKind::AAPCSSoft}) { - std::unique_ptr<TargetInfo> TI = createAArch64TargetInfo(TB, Kind); + std::unique_ptr<TargetInfo> TI = + createAArch64TargetInfo(TB, AArch64ABIOptions(Kind)); std::unique_ptr<FunctionInfo> FI = FunctionInfo::create(llvm::CallingConv::C, Void, {CannotPass}); TI->computeInfo(*FI); @@ -435,4 +477,122 @@ TEST_F(AArch64TargetInfoTest, ClassifyArgumentRecordCannotPassInRegisters) { } } +// Homogeneous floating-point aggregates of at most four members are returned +// directly under AAPCS and DarwinPCS. +TEST_F(AArch64TargetInfoTest, ClassifyReturnHFADirect) { + RecordFlags CXXFlags = passableRecordFlags(/*IsCXX=*/true); + + const ABIType *HFA2f = + makeRecord({FieldInfo(F32, 0), FieldInfo(F32, 32)}, 64, llvm::Align(4)); + const ABIType *HFA4d = makeRecord({FieldInfo(F64, 0), FieldInfo(F64, 64), + FieldInfo(F64, 128), FieldInfo(F64, 192)}, + 256, llvm::Align(8)); + const ABIType *HFA3arr = + makeRecord({FieldInfo(TB.getArrayType(F32, 3, /*SizeInBits=*/96), 0)}, 96, + llvm::Align(4)); + const ABIType *HFA2h = + makeRecord({FieldInfo(F16, 0), FieldInfo(F16, 16)}, 32, llvm::Align(2)); + const ABIType *HFANested = + makeRecord({FieldInfo(HFA2f, 0), FieldInfo(F32, 64)}, 96, llvm::Align(4)); + const ABIType *HFAZeroBF = + makeRecord({FieldInfo(I32, 0, /*IsBitField=*/true, /*BitFieldWidth=*/0), + FieldInfo(F32, 0), FieldInfo(F32, 32)}, + 64, 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, + 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)); + + // 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)); + const ABIType *HVA2xV3F32 = makeRecord( + {FieldInfo(V3F32, 0), FieldInfo(V3F32, 128)}, 256, llvm::Align(16)); + + // A 2x2 float matrix is four homogeneous float members. + const ABIType *HFAMatrix = + makeRecord({FieldInfo(Matrix, 0)}, 128, 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)); + + // C++ records: empty bases are skipped and non-empty bases contribute + // members. + const ABIType *EmptyRecord = makeRecord({}, 0, llvm::Align(1), CXXFlags); + const ABIType *HFAEmptyBase = + makeRecord({FieldInfo(F32, 0), FieldInfo(F32, 32)}, 64, 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}) { + std::unique_ptr<TargetInfo> TI = + createAArch64TargetInfo(TB, AArch64ABIOptions(Kind)); + for (const ABIType *RetTy : + {ComplexFloat, HFA2f, HFA4d, HFA3arr, HFA2h, HFANested, HFAZeroBF, + HFAUnion, HVA2x64, HVA2x128, HVA3x32, HVA2xV3F32, HFAMatrix, + HFAMatrix2, HFAEmptyBase, HFADerived}) { + std::unique_ptr<FunctionInfo> FI = + FunctionInfo::create(llvm::CallingConv::C, RetTy, {}); + FI->getReturnInfo() = ArgInfo::getIgnore(); + TI->computeInfo(*FI); + expectUncoercedDirect(FI->getReturnInfo()); + } + } +} + +// Records that cannot pass in registers are returned indirectly before HFA +// classification. +TEST_F(AArch64TargetInfoTest, ClassifyReturnCXXCannotPassInRegistersIndirect) { + std::unique_ptr<TargetInfo> TI = + createAArch64TargetInfo(TB, AArch64ABIOptions(AArch64ABIKind::AAPCS)); + + const ABIType *NonPassableHFA = + makeRecord({FieldInfo(F32, 0), FieldInfo(F32, 32)}, 64, llvm::Align(4), + RecordFlags::IsCXXRecord); + + // struct FloatBase { float f; }; + // struct VirtualDerived : virtual FloatBase {}; + // Virtual inheritance makes the copy constructor non-trivial, so the derived + // record cannot pass in registers even though its virtual base would + // 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), + passableRecordFlags(/*IsCXX=*/true)); + const ABIType *VirtualDerived = + makeRecord({}, 128, llvm::Align(8), RecordFlags::IsCXXRecord, + /*Bases=*/{}, /*VBases=*/{FieldInfo(FloatBase, 64)}); + + const struct { + const ABIType *RetTy; + llvm::Align ExpectedAlign; + } Cases[] = {{NonPassableHFA, llvm::Align(4)}, + {VirtualDerived, llvm::Align(8)}}; + + for (const auto &Case : Cases) { + std::unique_ptr<FunctionInfo> FI = + FunctionInfo::create(llvm::CallingConv::C, Case.RetTy, {}); + FI->getReturnInfo() = ArgInfo::getIgnore(); + TI->computeInfo(*FI); + expectNaturalAlignIndirect(FI->getReturnInfo(), Case.ExpectedAlign, + /*ByVal=*/false); + } +} + } // namespace _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
