Skip to content

Commit 3f37397

Browse files
authored
[clang][CodeGen] Fix MSVC ABI for classes with a deleted copy assignment operator (llvm#90547)
For global functions and static methods the MSVC ABI returns structs/classes with a deleted copy assignment operator indirectly. From local testing this ABI holds true for all currently supported architectures including ARM64EC.
1 parent c0d9efd commit 3f37397

File tree

3 files changed

+116
-1
lines changed

3 files changed

+116
-1
lines changed

clang/docs/ReleaseNotes.rst

+3
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ ABI Changes in This Version
7676
returning a class in a register. This affects some uses of std::pair.
7777
(#GH86384).
7878

79+
- Fixed Microsoft calling convention when returning classes that have a deleted
80+
copy assignment operator. Such a class should be returned indirectly.
81+
7982
AST Dumping Potentially Breaking Changes
8083
----------------------------------------
8184

clang/lib/CodeGen/MicrosoftCXXABI.cpp

+21-1
Original file line numberDiff line numberDiff line change
@@ -1122,7 +1122,22 @@ static bool isTrivialForMSVC(const CXXRecordDecl *RD, QualType Ty,
11221122
// No base classes
11231123
// No virtual functions
11241124
// Additionally, we need to ensure that there is a trivial copy assignment
1125-
// operator, a trivial destructor and no user-provided constructors.
1125+
// operator, a trivial destructor, no user-provided constructors and no
1126+
// deleted copy assignment operator.
1127+
1128+
// We need to cover two cases when checking for a deleted copy assignment
1129+
// operator.
1130+
//
1131+
// struct S { int& r; };
1132+
// The above will have an implicit copy assignment operator that is deleted
1133+
// and there will not be a `CXXMethodDecl` for the copy assignment operator.
1134+
// This is handled by the `needsImplicitCopyAssignment()` check below.
1135+
//
1136+
// struct S { S& operator=(const S&) = delete; int i; };
1137+
// The above will not have an implicit copy assignment operator that is
1138+
// deleted but there is a deleted `CXXMethodDecl` for the declared copy
1139+
// assignment operator. This is handled by the `isDeleted()` check below.
1140+
11261141
if (RD->hasProtectedFields() || RD->hasPrivateFields())
11271142
return false;
11281143
if (RD->getNumBases() > 0)
@@ -1131,13 +1146,18 @@ static bool isTrivialForMSVC(const CXXRecordDecl *RD, QualType Ty,
11311146
return false;
11321147
if (RD->hasNonTrivialCopyAssignment())
11331148
return false;
1149+
if (RD->needsImplicitCopyAssignment() && !RD->hasSimpleCopyAssignment())
1150+
return false;
11341151
for (const Decl *D : RD->decls()) {
11351152
if (auto *Ctor = dyn_cast<CXXConstructorDecl>(D)) {
11361153
if (Ctor->isUserProvided())
11371154
return false;
11381155
} else if (auto *Template = dyn_cast<FunctionTemplateDecl>(D)) {
11391156
if (isa<CXXConstructorDecl>(Template->getTemplatedDecl()))
11401157
return false;
1158+
} else if (auto *MethodDecl = dyn_cast<CXXMethodDecl>(D)) {
1159+
if (MethodDecl->isCopyAssignmentOperator() && MethodDecl->isDeleted())
1160+
return false;
11411161
}
11421162
}
11431163
if (RD->hasNonTrivialDestructor())
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// RUN: %clang_cc1 -triple x86_64-windows-msvc -ffreestanding -emit-llvm -O0 \
2+
// RUN: -x c++ -o - %s | FileCheck %s
3+
4+
int global_i = 0;
5+
6+
// Pass and return object with a reference type (pass directly, return indirectly).
7+
// CHECK: define dso_local void @"?f1@@YA?AUS1@@XZ"(ptr dead_on_unwind noalias writable sret(%struct.S1) align 8 {{.*}})
8+
// CHECK: call void @"?func1@@YA?AUS1@@U1@@Z"(ptr dead_on_unwind writable sret(%struct.S1) align 8 {{.*}}, i64 {{.*}})
9+
struct S1 {
10+
int& r;
11+
};
12+
13+
S1 func1(S1 x);
14+
S1 f1() {
15+
S1 x{ global_i };
16+
return func1(x);
17+
}
18+
19+
// Pass and return object with a reference type within an inner struct (pass directly, return indirectly).
20+
// CHECK: define dso_local void @"?f2@@YA?AUS2@@XZ"(ptr dead_on_unwind noalias writable sret(%struct.S2) align 8 {{.*}})
21+
// CHECK: call void @"?func2@@YA?AUS2@@U1@@Z"(ptr dead_on_unwind writable sret(%struct.S2) align 8 {{.*}}, i64 {{.*}})
22+
struct Inner {
23+
int& r;
24+
};
25+
26+
struct S2 {
27+
Inner i;
28+
};
29+
30+
S2 func2(S2 x);
31+
S2 f2() {
32+
S2 x{ { global_i } };
33+
return func2(x);
34+
}
35+
36+
// Pass and return object with a reference type (pass directly, return indirectly).
37+
// CHECK: define dso_local void @"?f3@@YA?AUS3@@XZ"(ptr dead_on_unwind noalias writable sret(%struct.S3) align 8 {{.*}})
38+
// CHECK: call void @"?func3@@YA?AUS3@@U1@@Z"(ptr dead_on_unwind writable sret(%struct.S3) align 8 {{.*}}, i64 {{.*}})
39+
struct S3 {
40+
const int& r;
41+
};
42+
43+
S3 func3(S3 x);
44+
S3 f3() {
45+
S3 x{ global_i };
46+
return func3(x);
47+
}
48+
49+
// Pass and return object with a reference type within an inner struct (pass directly, return indirectly).
50+
// CHECK: define dso_local void @"?f4@@YA?AUS4@@XZ"(ptr dead_on_unwind noalias writable sret(%struct.S4) align 8 {{.*}})
51+
// CHECK: call void @"?func4@@YA?AUS4@@U1@@Z"(ptr dead_on_unwind writable sret(%struct.S4) align 8 {{.*}}, i64 {{.*}})
52+
struct InnerConst {
53+
const int& r;
54+
};
55+
56+
struct S4 {
57+
InnerConst i;
58+
};
59+
60+
S4 func4(S4 x);
61+
S4 f4() {
62+
S4 x{ { global_i } };
63+
return func4(x);
64+
}
65+
66+
// Pass and return an object with an explicitly deleted copy assignment operator (pass directly, return indirectly).
67+
// CHECK: define dso_local void @"?f5@@YA?AUS5@@XZ"(ptr dead_on_unwind noalias writable sret(%struct.S5) align 4 {{.*}})
68+
// CHECK: call void @"?func5@@YA?AUS5@@U1@@Z"(ptr dead_on_unwind writable sret(%struct.S5) align 4 {{.*}}, i32 {{.*}})
69+
struct S5 {
70+
S5& operator=(const S5&) = delete;
71+
int i;
72+
};
73+
74+
S5 func5(S5 x);
75+
S5 f5() {
76+
S5 x{ 1 };
77+
return func5(x);
78+
}
79+
80+
// Pass and return an object with an explicitly defaulted copy assignment operator that is implicitly deleted (pass directly, return indirectly).
81+
// CHECK: define dso_local void @"?f6@@YA?AUS6@@XZ"(ptr dead_on_unwind noalias writable sret(%struct.S6) align 8 {{.*}})
82+
// CHECK: call void @"?func6@@YA?AUS6@@U1@@Z"(ptr dead_on_unwind writable sret(%struct.S6) align 8 {{.*}}, i64 {{.*}})
83+
struct S6 {
84+
S6& operator=(const S6&) = default;
85+
int& i;
86+
};
87+
88+
S6 func6(S6 x);
89+
S6 f6() {
90+
S6 x{ global_i };
91+
return func6(x);
92+
}

0 commit comments

Comments
 (0)