Skip to content

Commit 4590aa1

Browse files
authored
Merge pull request #65813 from hyp/eng/passInRegs
[cxx-interop] Itanium ABI C++ records should have address-only layout when they can't be passed in registers
2 parents cdc72fd + 7abd265 commit 4590aa1

File tree

7 files changed

+233
-10
lines changed

7 files changed

+233
-10
lines changed

lib/ClangImporter/ImportDecl.cpp

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2447,15 +2447,39 @@ namespace {
24472447
}
24482448

24492449
if (cxxRecordDecl) {
2450+
// FIXME: Swift right now uses AddressOnly type layout
2451+
// in a way that conflates C++ types
2452+
// that need to be destroyed or copied explicitly with C++
2453+
// types that have to be passed indirectly, because
2454+
// only AddressOnly types can be copied or destroyed using C++
2455+
// semantics. However, in actuality these two concepts are
2456+
// separate and don't map to one notion of AddressOnly type
2457+
// layout cleanly. We should reserve the use of AddressOnly
2458+
// type layout when types have to use C++ copy/move/destroy
2459+
// operations, but allow AddressOnly types to be passed
2460+
// directly as well. This will help unify the MSVC and
2461+
// Itanium difference here, and will allow us to support
2462+
// trivial_abi C++ types as well.
24502463
auto isNonTrivialForPurposeOfCalls =
24512464
[](const clang::CXXRecordDecl *decl) -> bool {
24522465
return decl->hasNonTrivialCopyConstructor() ||
24532466
decl->hasNonTrivialMoveConstructor() ||
24542467
!decl->hasTrivialDestructor();
24552468
};
2469+
auto isAddressOnlySwiftStruct =
2470+
[&](const clang::CXXRecordDecl *decl) -> bool {
2471+
// MSVC ABI allows non-trivially destroyed C++ types
2472+
// to be passed in register. This is not supported, as such
2473+
// type wouldn't be destroyed in Swift correctly. Therefore,
2474+
// force AddressOnly type layout using the old heuristic.
2475+
// FIXME: Support can pass in registers for MSVC correctly.
2476+
if (Impl.SwiftContext.LangOpts.Target.isWindowsMSVCEnvironment())
2477+
return isNonTrivialForPurposeOfCalls(decl);
2478+
return !decl->canPassInRegisters();
2479+
};
24562480
if (auto structResult = dyn_cast<StructDecl>(result))
24572481
structResult->setIsCxxNonTrivial(
2458-
isNonTrivialForPurposeOfCalls(cxxRecordDecl));
2482+
isAddressOnlySwiftStruct(cxxRecordDecl));
24592483

24602484
for (auto &getterAndSetter : Impl.GetterSetterMap[result]) {
24612485
auto getter = getterAndSetter.second.first;
@@ -2718,6 +2742,17 @@ namespace {
27182742
if (!result)
27192743
return nullptr;
27202744

2745+
if (decl->hasAttr<clang::TrivialABIAttr>()) {
2746+
// We cannot yet represent trivial_abi C++ records in Swift.
2747+
// Clang tells us such type can be passed in registers, so
2748+
// we avoid using AddressOnly type-layout for such type, which means
2749+
// that it then does not use C++'s copy and destroy semantics from
2750+
// Swift.
2751+
Impl.markUnavailable(cast<ValueDecl>(result),
2752+
"C++ classes with `trivial_abi` Clang attribute "
2753+
"are not yet available in Swift");
2754+
}
2755+
27212756
if (auto classDecl = dyn_cast<ClassDecl>(result)) {
27222757
validateForeignReferenceType(decl, classDecl);
27232758

lib/SIL/IR/SILFunctionType.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3212,6 +3212,12 @@ getIndirectCParameterConvention(const clang::ParmVarDecl *param) {
32123212
///
32133213
/// Generally, whether the parameter is +1 is handled before this.
32143214
static ParameterConvention getDirectCParameterConvention(clang::QualType type) {
3215+
if (auto *cxxRecord = type->getAsCXXRecordDecl()) {
3216+
// Directly passed non-trivially destroyed C++ record is consumed by the
3217+
// callee.
3218+
if (!cxxRecord->hasTrivialDestructor())
3219+
return ParameterConvention::Direct_Owned;
3220+
}
32153221
return ParameterConvention::Direct_Unowned;
32163222
}
32173223

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// RUN: rm -rf %t
2+
// RUN: split-file %s %t
3+
4+
// RUN: %target-swift-ide-test -print-module -module-to-print=Test -I %t/Inputs -source-filename=x -enable-experimental-cxx-interop | %FileCheck %s
5+
// RUN: %target-swift-frontend -typecheck -I %t/Inputs %t/test.swift -enable-experimental-cxx-interop -verify
6+
7+
//--- Inputs/module.modulemap
8+
module Test {
9+
header "test.h"
10+
requires cplusplus
11+
}
12+
13+
//--- Inputs/test.h
14+
15+
class TrivialABIRecord {
16+
int x = 0;
17+
public:
18+
TrivialABIRecord() {}
19+
~TrivialABIRecord() {
20+
}
21+
}
22+
__attribute__((trivial_abi));
23+
24+
// CHECK: @available(*, unavailable, message: "C++ classes with `trivial_abi` Clang attribute are not yet available in Swift")
25+
// CHECK-NEXT: struct TrivialABIRecord {
26+
27+
//--- test.swift
28+
29+
import Test
30+
31+
func test() {
32+
let _ = TrivialABIRecord() // expected-error{{'TrivialABIRecord' is unavailable: C++ classes with `trivial_abi` Clang attribute are not yet available in Swift}}
33+
}

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

0 commit comments

Comments
 (0)