diff --git a/clang/lib/CodeGen/CGCXXABI.h b/clang/lib/CodeGen/CGCXXABI.h --- a/clang/lib/CodeGen/CGCXXABI.h +++ b/clang/lib/CodeGen/CGCXXABI.h @@ -625,6 +625,11 @@ virtual std::pair LoadVTablePtr(CodeGenFunction &CGF, Address This, const CXXRecordDecl *RD) = 0; + + virtual bool + isPermittedToBeHomogeneousAggregate(const CXXRecordDecl *RD) const { + return true; + }; }; // Create an instance of a C++ ABI class: diff --git a/clang/lib/CodeGen/MicrosoftCXXABI.cpp b/clang/lib/CodeGen/MicrosoftCXXABI.cpp --- a/clang/lib/CodeGen/MicrosoftCXXABI.cpp +++ b/clang/lib/CodeGen/MicrosoftCXXABI.cpp @@ -771,6 +771,9 @@ LoadVTablePtr(CodeGenFunction &CGF, Address This, const CXXRecordDecl *RD) override; + virtual bool + isPermittedToBeHomogeneousAggregate(const CXXRecordDecl *RD) const override; + private: typedef std::pair VFTableIdTy; typedef llvm::DenseMap VTablesMapTy; @@ -1070,7 +1073,7 @@ return isDeletingDtor(GD); } -static bool isCXX14Aggregate(const CXXRecordDecl *RD) { +static bool isTrivialForAArch64MSVC(const CXXRecordDecl *RD) { // For AArch64, we use the C++14 definition of an aggregate, so we also // check for: // No private or protected non static data members. @@ -1107,7 +1110,7 @@ bool isTrivialForABI = RD->isPOD(); bool isAArch64 = CGM.getTarget().getTriple().isAArch64(); if (isAArch64) - isTrivialForABI = RD->canPassInRegisters() && isCXX14Aggregate(RD); + isTrivialForABI = RD->canPassInRegisters() && isTrivialForAArch64MSVC(RD); // MSVC always returns structs indirectly from C++ instance methods. bool isIndirectReturn = !isTrivialForABI || FI.isInstanceMethod(); @@ -4358,3 +4361,11 @@ performBaseAdjustment(CGF, This, QualType(RD->getTypeForDecl(), 0)); return {CGF.GetVTablePtr(This, CGM.Int8PtrTy, RD), RD}; } + +bool MicrosoftCXXABI::isPermittedToBeHomogeneousAggregate( + const CXXRecordDecl *CXXRD) const { + // MSVC Windows on Arm64 considers a type not HFA if it is not an + // aggregate according to the C++14 spec. This is not consistent with the + // AAPCS64, but is defacto spec on that platform. + return isTrivialForAArch64MSVC(CXXRD); +} diff --git a/clang/lib/CodeGen/TargetInfo.cpp b/clang/lib/CodeGen/TargetInfo.cpp --- a/clang/lib/CodeGen/TargetInfo.cpp +++ b/clang/lib/CodeGen/TargetInfo.cpp @@ -5120,8 +5120,12 @@ Members = 0; - // If this is a C++ record, check the bases first. + // If this is a C++ record, check the properties of the record such as + // bases and ABI specific restrictions if (const CXXRecordDecl *CXXRD = dyn_cast(RD)) { + if (!getCXXABI().isPermittedToBeHomogeneousAggregate(CXXRD)) + return false; + for (const auto &I : CXXRD->bases()) { // Ignore empty records. if (isEmptyRecord(getContext(), I.getType(), true)) diff --git a/clang/test/CodeGenCXX/homogeneous-aggregates.cpp b/clang/test/CodeGenCXX/homogeneous-aggregates.cpp --- a/clang/test/CodeGenCXX/homogeneous-aggregates.cpp +++ b/clang/test/CodeGenCXX/homogeneous-aggregates.cpp @@ -104,3 +104,66 @@ // ARM32: define arm_aapcs_vfpcc void @_Z19with_empty_bitfield20HVAWithEmptyBitField(%struct.HVAWithEmptyBitField %a.coerce) // X64: define dso_local x86_vectorcallcc void @"\01_Z19with_empty_bitfield20HVAWithEmptyBitField@@16"(%struct.HVAWithEmptyBitField inreg %a.coerce) void CC with_empty_bitfield(HVAWithEmptyBitField a) {} + +namespace pr47611 { +// MSVC on Arm includes "isCXX14Aggregate" as part of its definition of +// Homogeneous Floating-point Aggregate (HFA). Additionally, it has a different +// handling of C++14 aggregates, which can lead to confusion. + +// Pod is a trivial HFA. +struct Pod { + double b[2]; +}; +// Not an aggregate according to C++14 spec => not HFA according to MSVC. +struct NotCXX14Aggregate { + NotCXX14Aggregate(); + Pod p; +}; +// NotPod is a C++14 aggregate. But not HFA, because it contains +// NotCXX14Aggregate (which itself is not HFA because it's not a C++14 +// aggregate). +struct NotPod { + NotCXX14Aggregate x; +}; +struct Empty {}; +// A class with a base is returned using the sret calling convetion by MSVC. +struct HasEmptyBase : public Empty { + double b[2]; +}; +struct HasPodBase : public Pod {}; +// WOA64-LABEL: define dso_local %"struct.pr47611::Pod" @"?copy@pr47611@@YA?AUPod@1@PEAU21@@Z"(%"struct.pr47611::Pod"* %x) +Pod copy(Pod *x) { return *x; } // MSVC: ldp d0,d1,[x0], Clang: ldp d0,d1,[x0] +// WOA64-LABEL: define dso_local void @"?copy@pr47611@@YA?AUNotCXX14Aggregate@1@PEAU21@@Z"(%"struct.pr47611::NotCXX14Aggregate"* inreg noalias sret(%"struct.pr47611::NotCXX14Aggregate") align 8 %agg.result, %"struct.pr47611::NotCXX14Aggregate"* %x) +NotCXX14Aggregate copy(NotCXX14Aggregate *x) { return *x; } // MSVC: stp x8,x9,[x0], Clang: str q0,[x0] +// WOA64-LABEL: define dso_local [2 x i64] @"?copy@pr47611@@YA?AUNotPod@1@PEAU21@@Z"(%"struct.pr47611::NotPod"* %x) +NotPod copy(NotPod *x) { return *x; } +// WOA64-LABEL: define dso_local void @"?copy@pr47611@@YA?AUHasEmptyBase@1@PEAU21@@Z"(%"struct.pr47611::HasEmptyBase"* inreg noalias sret(%"struct.pr47611::HasEmptyBase") align 8 %agg.result, %"struct.pr47611::HasEmptyBase"* %x) +HasEmptyBase copy(HasEmptyBase *x) { return *x; } +// WOA64-LABEL: define dso_local void @"?copy@pr47611@@YA?AUHasPodBase@1@PEAU21@@Z"(%"struct.pr47611::HasPodBase"* inreg noalias sret(%"struct.pr47611::HasPodBase") align 8 %agg.result, %"struct.pr47611::HasPodBase"* %x) +HasPodBase copy(HasPodBase *x) { return *x; } + +void call_copy_pod(Pod *pod) { + *pod = copy(pod); + // WOA64-LABEL: %{{.*}} = call %"struct.pr47611::Pod" @"?copy@pr47611@@YA?AUPod@1@PEAU21@@Z"(%"struct.pr47611::Pod"* %{{.*}}) +} + +void call_copy_notcxx14aggregate(NotCXX14Aggregate *notcxx14aggregate) { + *notcxx14aggregate = copy(notcxx14aggregate); + // WOA64-LABEL: call void @"?copy@pr47611@@YA?AUNotCXX14Aggregate@1@PEAU21@@Z"(%"struct.pr47611::NotCXX14Aggregate"* inreg sret(%"struct.pr47611::NotCXX14Aggregate") align 8 %{{.*}}, %"struct.pr47611::NotCXX14Aggregate"* %{{.*}}) +} + +void call_copy_notpod(NotPod *notPod) { + *notPod = copy(notPod); + // WOA64-LABEL: %{{.*}} = call [2 x i64] @"?copy@pr47611@@YA?AUNotPod@1@PEAU21@@Z"(%"struct.pr47611::NotPod"* %{{.*}}) +} + +void call_copy_hasemptybase(HasEmptyBase *hasEmptyBase) { + *hasEmptyBase = copy(hasEmptyBase); + // WOA64-LABEL: call void @"?copy@pr47611@@YA?AUHasEmptyBase@1@PEAU21@@Z"(%"struct.pr47611::HasEmptyBase"* inreg sret(%"struct.pr47611::HasEmptyBase") align 8 %{{.*}}, %"struct.pr47611::HasEmptyBase"* %{{.*}}) +} + +void call_copy_haspodbase(HasPodBase *hasPodBase) { + *hasPodBase = copy(hasPodBase); + // WOA64-LABEL: call void @"?copy@pr47611@@YA?AUHasPodBase@1@PEAU21@@Z"(%"struct.pr47611::HasPodBase"* inreg sret(%"struct.pr47611::HasPodBase") align 8 %{{.*}}, %"struct.pr47611::HasPodBase"* %{{.*}}) +} +}; // namespace pr47611 diff --git a/llvm/test/CodeGen/AArch64/arm64-windows-calls.ll b/llvm/test/CodeGen/AArch64/arm64-windows-calls.ll --- a/llvm/test/CodeGen/AArch64/arm64-windows-calls.ll +++ b/llvm/test/CodeGen/AArch64/arm64-windows-calls.ll @@ -98,3 +98,74 @@ %this1 = load %class.C*, %class.C** %this.addr, align 8 ret void } + +; The following tests correspond to tests in +; clang/test/CodeGenCXX/microsoft-abi-sret-and-byval.cpp + +; Pod is a trivial HFA +%struct.Pod = type { [2 x double] } +; Not an aggregate according to C++14 spec => not HFA according to MSVC +%struct.NotCXX14Aggregate = type { %struct.Pod } +; NotPod is a C++14 aggregate. But not HFA, because it contains +; NotCXX14Aggregate (which itself is not HFA because it's not a C++14 +; aggregate). +%struct.NotPod = type { %struct.NotCXX14Aggregate } + +define dso_local %struct.Pod @copy_pod(%struct.Pod* %x) { + %x1 = load %struct.Pod, %struct.Pod* %x, align 8 + ret %struct.Pod %x1 +; CHECK-LABEL: ldp d0, d1, [x0] +} + +declare void @llvm.memcpy.p0i8.p0i8.i64(i8* noalias nocapture writeonly, i8* noalias nocapture readonly, i64, i1 immarg) + +define dso_local void +@copy_notcxx14aggregate(%struct.NotCXX14Aggregate* inreg noalias sret(%struct.NotCXX14Aggregate) align 8 %agg.result, + %struct.NotCXX14Aggregate* %x) { + %1 = bitcast %struct.NotCXX14Aggregate* %agg.result to i8* + %2 = bitcast %struct.NotCXX14Aggregate* %x to i8* + call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 8 %1, i8* align 8 %2, i64 16, i1 false) + ret void +; CHECK-LABEL: str q0, [x0] +} + +define dso_local [2 x i64] @copy_notpod(%struct.NotPod* %x) { + %x1 = bitcast %struct.NotPod* %x to [2 x i64]* + %x2 = load [2 x i64], [2 x i64]* %x1 + ret [2 x i64] %x2 +; CHECK-LABEL: ldp x8, x1, [x0] +; CHECK: mov x0, x8 +} + +@Pod = external global %struct.Pod + +define void @call_copy_pod() { + %x = call %struct.Pod @copy_pod(%struct.Pod* @Pod) + store %struct.Pod %x, %struct.Pod* @Pod + ret void + ; CHECK-LABEL: bl copy_pod + ; CHECK-NEXT: stp d0, d1, [{{.*}}] +} + +@NotCXX14Aggregate = external global %struct.NotCXX14Aggregate + +define void @call_copy_notcxx14aggregate() { + %x = alloca %struct.NotCXX14Aggregate + call void @copy_notcxx14aggregate(%struct.NotCXX14Aggregate* %x, %struct.NotCXX14Aggregate* @NotCXX14Aggregate) + %x1 = load %struct.NotCXX14Aggregate, %struct.NotCXX14Aggregate* %x + store %struct.NotCXX14Aggregate %x1, %struct.NotCXX14Aggregate* @NotCXX14Aggregate + ret void + ; CHECK-LABEL: bl copy_notcxx14aggregate + ; CHECK-NEXT: ldp {{.*}}, {{.*}}, [sp] +} + +@NotPod = external global %struct.NotPod + +define void @call_copy_notpod() { + %x = call [2 x i64] @copy_notpod(%struct.NotPod* @NotPod) + %notpod = bitcast %struct.NotPod* @NotPod to [2 x i64]* + store [2 x i64] %x, [2 x i64]* %notpod + ret void + ; CHECK-LABEL: bl copy_notpod + ; CHECK-NEXT: stp x0, x1, [{{.*}}] +}