Skip to content

Commit 49d7e04

Browse files
committed
[cxx-interop] C++ records should have address-only layout when they can't be passed in registers
This ensures that a C++ record with only ObjC ARC pointers with trivial other members is passed by value in SIL Fixes swiftlang#61929
1 parent d7e18cb commit 49d7e04

File tree

6 files changed

+163
-16
lines changed

6 files changed

+163
-16
lines changed

lib/ClangImporter/ImportDecl.cpp

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2427,15 +2427,9 @@ namespace {
24272427
}
24282428

24292429
if (cxxRecordDecl) {
2430-
auto isNonTrivialForPurposeOfCalls =
2431-
[](const clang::CXXRecordDecl *decl) -> bool {
2432-
return decl->hasNonTrivialCopyConstructor() ||
2433-
decl->hasNonTrivialMoveConstructor() ||
2434-
!decl->hasTrivialDestructor();
2435-
};
24362430
if (auto structResult = dyn_cast<StructDecl>(result))
24372431
structResult->setIsCxxNonTrivial(
2438-
isNonTrivialForPurposeOfCalls(cxxRecordDecl));
2432+
!cxxRecordDecl->canPassInRegisters());
24392433

24402434
for (auto &getterAndSetter : Impl.GetterSetterMap[result]) {
24412435
auto getter = getterAndSetter.second.first;

lib/SIL/IR/SILFunctionType.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3137,6 +3137,12 @@ getIndirectCParameterConvention(const clang::ParmVarDecl *param) {
31373137
///
31383138
/// Generally, whether the parameter is +1 is handled before this.
31393139
static ParameterConvention getDirectCParameterConvention(clang::QualType type) {
3140+
if (auto *cxxRecord = type->getAsCXXRecordDecl()) {
3141+
// Directly passed non-trivially destroyed C++ record is consumed by the
3142+
// callee.
3143+
if (!cxxRecord->hasTrivialDestructor())
3144+
return ParameterConvention::Direct_Owned;
3145+
}
31403146
return ParameterConvention::Direct_Unowned;
31413147
}
31423148

test/Interop/Cxx/objc-correctness/Inputs/cxx-class-with-arc-fields-ctor.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,41 @@ struct S {
55
NSString *_Nullable B;
66
NSString *_Nullable C;
77

8+
#ifdef S_NONTRIVIAL_DESTRUCTOR
9+
~S() {}
10+
#endif
11+
812
void dump() const {
913
printf("%s\n", [A UTF8String]);
1014
printf("%s\n", [B UTF8String]);
1115
printf("%s\n", [C UTF8String]);
1216
}
1317
};
1418

19+
inline void takeSFunc(S s) {
20+
s.dump();
21+
}
22+
23+
struct NonTrivialLogDestructor {
24+
int x = 0;
25+
26+
~NonTrivialLogDestructor() {
27+
printf("~NonTrivialLogDestructor %d\n", x);
28+
}
29+
};
30+
31+
@interface ClassWithNonTrivialDestructorIvar: NSObject
32+
33+
- (ClassWithNonTrivialDestructorIvar * _Nonnull)init;
34+
35+
- (void)takesS:(S)s;
36+
37+
@end
38+
39+
struct ReferenceStructToClassWithNonTrivialLogDestructorIvar {
40+
ClassWithNonTrivialDestructorIvar *_Nonnull x;
41+
42+
#ifdef S_NONTRIVIAL_DESTRUCTOR
43+
~ReferenceStructToClassWithNonTrivialLogDestructorIvar() {}
44+
#endif
45+
};
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#import <Foundation/Foundation.h>
2+
#import "cxx-class-with-arc-fields-ctor.h"
3+
4+
@implementation ClassWithNonTrivialDestructorIvar {
5+
NonTrivialLogDestructor value;
6+
};
7+
8+
- (ClassWithNonTrivialDestructorIvar *)init {
9+
self->value.x = 21;
10+
return self;
11+
}
12+
13+
- (void)takesS:(S)s {
14+
printf("takesS!\n");
15+
s.dump();
16+
}
17+
18+
@end
Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// RUN: %target-swift-ide-test -print-module -module-to-print=CxxClassWithNSStringInit -I %S/Inputs -source-filename=x -enable-experimental-cxx-interop -enable-objc-interop | %FileCheck -check-prefix=CHECK-IDE-TEST %s
2-
// RUN: %target-swift-frontend -I %S/Inputs -enable-experimental-cxx-interop -emit-ir %s -Xcc -fignore-exceptions | %FileCheck %s
3-
2+
// RUN: %target-swift-frontend -I %S/Inputs -enable-experimental-cxx-interop -emit-sil %s -Xcc -fignore-exceptions | %FileCheck --check-prefix=SIL-TRIVIAL %s
3+
// RUN: %target-swift-frontend -I %S/Inputs -enable-experimental-cxx-interop -emit-sil %s -Xcc -fignore-exceptions -Xcc -DS_NONTRIVIAL_DESTRUCTOR | %FileCheck --check-prefix=SIL-NONTRIVIAL %s
4+
// RUN: %target-swift-frontend -I %S/Inputs -enable-experimental-cxx-interop -emit-ir %s -Xcc -fignore-exceptions | %FileCheck --check-prefix=IR-TRIVIAL %s
5+
// RUN: %target-swift-frontend -I %S/Inputs -enable-experimental-cxx-interop -emit-ir %s -Xcc -fignore-exceptions -Xcc -DS_NONTRIVIAL_DESTRUCTOR | %FileCheck --check-prefix=IR-NONTRIVIAL %s
46

57
// REQUIRES: objc_interop
68

@@ -15,11 +17,45 @@ import CxxClassWithNSStringInit
1517
// CHECK-IDE-TEST: var C: NSString?
1618
// CHECK-IDE-TEST: }
1719

18-
var foo: NSString? = "foo"
19-
var bar: NSString? = "bar"
20-
var baz: NSString? = "baz"
21-
var s = S(A: foo, B: bar, C: baz)
22-
s.dump()
20+
func testSdump() {
21+
var foo: NSString? = "foo"
22+
var bar: NSString? = "bar"
23+
var baz: NSString? = "baz"
24+
var s = S(A: foo, B: bar, C: baz)
25+
s.dump()
26+
ClassWithNonTrivialDestructorIvar().takesS(s)
27+
takeSFunc(s)
28+
}
29+
30+
testSdump()
31+
32+
// SIL-TRIVIAL: function_ref @_ZNK1S4dumpEv : $@convention(cxx_method) (@in_guaranteed S) -> ()
33+
// SIL-TRIVIAL-NEXT: apply %{{.*}}(%{{.*}}) : $@convention(cxx_method) (@in_guaranteed S) -> ()
34+
// SIL-TRIVIAL: $@convention(objc_method) (@owned S, ClassWithNonTrivialDestructorIvar) -> ()
35+
// SIL-TRIVIAL-NEXT: apply %{{.*}}(%{{.*}}) : $@convention(objc_method) (@owned S, ClassWithNonTrivialDestructorIvar) -> ()
36+
// SIL-TRIVIAL: function_ref @_Z9takeSFunc : $@convention(c) (@owned S) -> ()
37+
// SIL-TRIVIAL-NEXT: apply %{{.*}}(%{{.*}}) : $@convention(c) (@owned S) -> ()
38+
39+
// SIL-NONTRIVIAL: function_ref @_ZNK1S4dumpEv : $@convention(cxx_method) (@in_guaranteed S) -> ()
40+
// SIL-NONTRIVIAL-NEXT: apply %{{.*}}(%{{.*}}) : $@convention(cxx_method) (@in_guaranteed S) -> ()
41+
// SIL-NONTRIVIAL: $@convention(objc_method) (@in S, ClassWithNonTrivialDestructorIvar) -> ()
42+
// SIL-NONTRIVIAL-NEXT: apply %{{.*}}(%{{.*}}) : $@convention(objc_method) (@in S, ClassWithNonTrivialDestructorIvar) -> ()
43+
// SIL-NONTRIVIAL: function_ref @_Z9takeSFunc : $@convention(c) (@in S) -> ()
44+
// SIL-NONTRIVIAL-NEXT: apply %{{.*}}(%{{.*}}) : $@convention(c) (@in S) -> ()
45+
46+
47+
// IR-TRIVIAL-LABEL: define {{.*}} swiftcc void @"$s4main9testSdumpyyF"()
48+
// IR-TRIVIAL-NOT: @_ZN1SC1ERKS_
49+
// IR-TRIVIAL: call {{.*}} @_ZNK1S4dumpEv
50+
// IR-TRIVIAL: call {{.*}} @"$sSo1SVWOh"
51+
52+
// IR-TRIVIAL-LABEL: define linkonce_odr {{.*}} @"$sSo1SVWOh"(
53+
// IR-TRIVIAL: @llvm.objc.release
54+
// IR-TRIVIAL: @llvm.objc.release
55+
// IR-TRIVIAL: @llvm.objc.release
56+
// IR-TRIVIAL: }
2357

24-
// CHECK: call {{.*}} @_ZN1SC1ERKS_
25-
// CHECK: call {{.*}} @_ZNK1S4dumpEv
58+
// IR-NONTRIVIAL-LABEL: define {{.*}} swiftcc void @"$s4main9testSdumpyyF"()
59+
// IR-NONTRIVIAL: call {{.*}} @_ZN1SC1ERKS_
60+
// IR-NONTRIVIAL: call {{.*}} @_ZNK1S4dumpEv
61+
// IR-NONTRIVIAL: call {{.*}} @_ZN1SD1Ev
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// RUN: %empty-directory(%t2)
2+
3+
// RUN: %target-interop-build-clangxx -c %S/Inputs/objc-class-with-non-trivial-cxx-record.mm -o %t2/objc-class-impl.o -fobjc-arc
4+
5+
// RUN: %target-run-simple-swift(-I %S/Inputs -Xfrontend -enable-experimental-cxx-interop -Xcc -fignore-exceptions -Xlinker %t2/objc-class-impl.o) | %FileCheck %s
6+
// RUN: %target-run-simple-swift(-I %S/Inputs -Xfrontend -enable-experimental-cxx-interop -Xcc -fignore-exceptions -O -Xlinker %t2/objc-class-impl.o) | %FileCheck %s
7+
// RUN: %target-run-simple-swift(-I %S/Inputs -Xfrontend -enable-experimental-cxx-interop -Xcc -fignore-exceptions -Xcc -DS_NONTRIVIAL_DESTRUCTOR -Xlinker %t2/objc-class-impl.o) | %FileCheck %s
8+
//
9+
// REQUIRES: executable_test
10+
// REQUIRES: objc_interop
11+
12+
import Foundation
13+
import CxxClassWithNSStringInit
14+
15+
func testS() {
16+
let copy: S
17+
do {
18+
let foo: NSString? = "super long string actually allocated"
19+
let bar: NSString? = "bar"
20+
let baz: NSString? = "baz"
21+
var s = S(A: foo, B: bar, C: baz)
22+
s.dump()
23+
copy = s
24+
}
25+
print("after scope")
26+
copy.dump()
27+
print("takeSFunc")
28+
takeSFunc(copy)
29+
}
30+
31+
@inline(never)
32+
func blackHole<T>(_ x: T) {
33+
34+
}
35+
36+
func testReferenceStructToClassWithNonTrivialLogDestructorIvar() {
37+
print("testReferenceStructToClassWithNonTrivialLogDestructorIvar")
38+
let m = ReferenceStructToClassWithNonTrivialLogDestructorIvar(x: ClassWithNonTrivialDestructorIvar())
39+
m.x.takesS(S(A: "hello world two", B: "bar", C: "baz"))
40+
blackHole(m)
41+
}
42+
43+
testS()
44+
testReferenceStructToClassWithNonTrivialLogDestructorIvar()
45+
46+
// CHECK: super long string actually allocated
47+
// CHECK-NEXT: bar
48+
// CHECK-NEXT: baz
49+
// CHECK-NEXT: after scope
50+
// CHECK-NEXT: super long string actually allocated
51+
// CHECK-NEXT: bar
52+
// CHECK-NEXT: baz
53+
// CHECK-NEXT: takeSFunc
54+
// CHECK-NEXT: super long string actually allocated
55+
// CHECK-NEXT: bar
56+
// CHECK-NEXT: baz
57+
// CHECK-NEXT: testReferenceStructToClassWithNonTrivialLogDestructorIvar
58+
// CHECK-NEXT: takesS!
59+
// CHECK-NEXT: hello world two
60+
// CHECK-NEXT: bar
61+
// CHECK-NEXT: baz
62+
// CHECK-NEXT: ~NonTrivialLogDestructor 21

0 commit comments

Comments
 (0)