Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[cxx-interop] Use user defined copy constructor to copy C++ objects. #32378

Merged
merged 1 commit into from
Feb 4, 2021

Conversation

zoecarver
Copy link
Contributor

Calls user-defined copy constructor when present. Based on #30630 (probably best to wait until that lands to review this).

@zoecarver zoecarver added the c++ interop Feature: Interoperability with C++ label Jun 14, 2020
@zoecarver zoecarver marked this pull request as draft June 14, 2020 20:25
@@ -410,7 +410,19 @@ clang::QualType ClangTypeConverter::visitProtocolType(ProtocolType *type) {
// Metatypes can be converted to Class when they are metatypes for concrete
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case it wasn't clear from the summary, most of the changes here are from #30630.

Copy link
Contributor

@martinboehme martinboehme left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I was going to do this at some point, but hadn't gotten round to it yet.

lib/ClangImporter/ImportDecl.cpp Outdated Show resolved Hide resolved
lib/ClangImporter/ImportDecl.cpp Outdated Show resolved Hide resolved
lib/IRGen/GenStruct.cpp Outdated Show resolved Hide resolved
lib/IRGen/GenStruct.cpp Outdated Show resolved Hide resolved
lib/IRGen/GenStruct.cpp Outdated Show resolved Hide resolved
test/Interop/Cxx/constructor/copy-constructor-irgen.swift Outdated Show resolved Hide resolved
test/Interop/Cxx/constructor/copy-constructor-irgen.swift Outdated Show resolved Hide resolved
test/Interop/Cxx/constructor/copy-constructor-irgen.swift Outdated Show resolved Hide resolved
test/Interop/Cxx/constructor/copy-constructor-irgen.swift Outdated Show resolved Hide resolved
test/Interop/Cxx/constructor/copy-constructor-irgen.swift Outdated Show resolved Hide resolved
@zoecarver
Copy link
Contributor Author

Thanks for the reviews! I fixed a few of the comments. I'll fix the rest tomorrow (and add some related witness table changes from #32291).

@zoecarver zoecarver force-pushed the cxx/copy-const branch 3 times, most recently from e4fb5c4 to 1ef546b Compare June 17, 2020 15:54
@zoecarver
Copy link
Contributor Author

@gribozavr and @martinboehme thank you for your comments and patience. I think I've (finally) resolved all the comments (except for the thunk/Microsoft ABI issue). Sorry for the delay.

Copy link
Contributor

@martinboehme martinboehme left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Responding to one of your code-level comments here, as github apparently won't allow me to respond to the comment thread any more:

We could also import it as a __double_underscore method which should help with overload resolution. Then just use it's function signature and ignore it.

Yes, something like that I think. I'm not super-familiar with the rules that Swift has here (to make sure we're not generating a name that could conflict with anything else) -- @gribozavr can probably say more about this.

Alternatively, we could try to build the "correct" function type in IRGen and then use that in the signature argument.

Yes, that's what we would need to do. But I think that would end up being quite a bit more code than the alternative we're discussing above. I've also pointed out an additional name-mangling issue in one of my new comments.

I think it's easier to just import the copy constructor as a SIL function, emit a call to that, and have the existing code take care of the rest.

lib/ClangImporter/ImportDecl.cpp Outdated Show resolved Hide resolved
if (ctor->isDeleted())
return nullptr;
}
if (ctor->getAccess() != clang::AS_public) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have to admit I don't understand the logic that existed here before this PR, and which was introduced by #31707.

AFAICT, the definition of "trivially copyable" says nothing about the access level of the copy constructor. It does say something about the copy constructor being deleted, namely that this is compatible with the class being trivially copyable.

Beyond that, I'm not sure why simply taking the result of cxxRecordDecl->isTriviallyCopyable() isn't sufficient here.

@MForster I think you introduced this logic -- can you comment?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[class.prop]/p1 defines a trivially copyable class as a class, "that has at least one eligible copy constructor..." I think the important word there is eligible. A private copy constructor is not eligible.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that's how C++ defines "eligible":

https://eel.is/c++draft/class#special-6

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True. However, I think we have to still check for public access level, since it would be otherwise quite weird that Swift can use a private constructor -- even if it is trivial.

lib/ClangImporter/ImportDecl.cpp Outdated Show resolved Hide resolved
lib/ClangImporter/ImportDecl.cpp Outdated Show resolved Hide resolved
lib/ClangImporter/ImportDecl.cpp Show resolved Hide resolved
@@ -0,0 +1,21 @@
// Target-specific tests for C++ copy constructor code generation.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gribozavr Did you really intend that this test should be in the class (not value-witness-table) directory?

This forces us to duplicate the definitions of the C++ classes between the two directories, which seems suboptimal.

test/Interop/Cxx/value-witness-table/copy-basic.swift Outdated Show resolved Hide resolved
test/Interop/Cxx/value-witness-table/copy-basic.swift Outdated Show resolved Hide resolved
test/Interop/Cxx/value-witness-table/copy-basic.swift Outdated Show resolved Hide resolved
// RUN: %target-swift-frontend -enable-cxx-interop -I %S/Inputs %s -emit-ir | %FileCheck %s

// REQUIRES: CPU=x86_64
// REQUIRES: objc_interop
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a shame that this test needs to be so platform-specific.

If you change the definitions of your C++ test classes a bit, I think you should be able to use a strategy similar to the one used here:

https://github.com/apple/swift/pull/30630/files#diff-496932cc8ce48ca2aa1c0a25fd7d1d8b

This would then also allow you to tests for platforms that require implicit parameters to be inserted.

I think the main obstacle to importing your current C++ classes without a stdlib present is that they use the C++ type int in the interface, and the corresponding CInt is defined in stdlib. So I'd suggest defining variants of these classes that don't use int in the interface, and using them for this test.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even without an int member, they need the stdlib to represent the reference types (with an UnsafePointer). I tried updating this test for multiple targets but, I don't think this will work (without the stdlib). I even tried mocking up a basic UnsafePointer in the test file but, that didn't seem to work either (and it seems very fragile).

Another possibility is I could duplicate this test and have a REQUIRES comment for each target (i.e., have three tests for three targets). WDTY the best solution is?

@zoecarver zoecarver force-pushed the cxx/copy-const branch 3 times, most recently from 4b87be5 to ecd1674 Compare June 24, 2020 02:15
@zoecarver
Copy link
Contributor Author

Yes, that's what we would need to do. But I think that would end up being quite a bit more code than the alternative we're discussing above. I've also pointed out an additional name-mangling issue in one of my new comments.

I figured out all we really need to do is create the SIL function type and that didn't seem to be too much work. I also got it working by importing the constructor like any other, so if you'd rather we can do that (but I like this way better).

FWIW one issue I ran into when importing the constructor as a SIL function is that Swift will try to remove the function unless there is a use of it (or maybe it just lazily SILGens it-- either way it doesn't seem to exist when I dump the SIL if I don't explicitly call it somewhere).

@zoecarver zoecarver marked this pull request as ready for review June 24, 2020 16:10
@shahmishal
Copy link
Member

Please update the base branch to main by Oct 5th otherwise the pull request will be closed automatically.

  • How to change the base branch: (Link)
  • More detail about the branch update: (Link)

@zoecarver zoecarver changed the base branch from master to main October 2, 2020 04:03
@zoecarver
Copy link
Contributor Author

@swift-ci please smoke test Linux platform.

@zoecarver
Copy link
Contributor Author

@swift-ci please test

@zoecarver
Copy link
Contributor Author

@swift-ci please test

@zoecarver
Copy link
Contributor Author

@hlopko and @gribozavr friendly ping. This is ready for review when you're ready to review it :)

@swift-ci
Copy link
Contributor

Build failed
Swift Test OS X Platform
Git Sha - 1d2ac7860d014eaf5776a20d1459f8d26e879710

@swift-ci
Copy link
Contributor

Build failed
Swift Test Linux Platform
Git Sha - 1d2ac7860d014eaf5776a20d1459f8d26e879710

@@ -18,7 +18,7 @@
// This definition is a placeholder for importing into Swift.
// It provides size and alignment but cannot be manipulated safely there.
typedef struct {
__swift_uintptr_t refCounts SWIFT_ATTRIBUTE_UNAVAILABLE;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, in C-mode this attribute is meaningless which is why we didn't see the error before now. If we want to make sure that in runtime mode we don't use this type, I'll need to create a separate and more involved fix. Let me know if that needs to be done.

Another "fix" could be to only add SWIFT_ATTRIBUTE_UNAVAILABLE when ifndef __swift__ .

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, in C-mode this attribute is meaningless which is why we didn't see the error before now.

Which error?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we have a reference type, for example an array, we try to add an implicit copy constructor in C++ mode. When we try to do this, we get an error that refCounts is unreachable:

swift-source/build/Ninja-ReleaseAssert/swift-macosx-arm64/lib/swift/shims/RefCount.h:20:9: error: 'refCounts' is unavailable
typedef struct {
        ^
swift-source/build/Ninja-ReleaseAssert/swift-macosx-arm64/lib/swift/shims/HeapObject.h:45:8: note: in implicit copy constructor for 'InlineRefCountsPlaceholder' first required here
struct HeapObject {
       ^
swift-source/build/Ninja-ReleaseAssert/swift-macosx-arm64/lib/swift/shims/GlobalObjects.h:38:8: note: in implicit copy constructor for 'HeapObject' first required here
struct _SwiftEmptyArrayStorage {
       ^
<unknown>:0: note: in implicit copy constructor for '_SwiftEmptyArrayStorage' first required here
swift-source/build/Ninja-ReleaseAssert/swift-macosx-arm64/lib/swift/shims/RefCount.h:21:21: note: 'refCounts' has been explicitly marked unavailable here
  __swift_uintptr_t refCounts SWIFT_ATTRIBUTE_UNAVAILABLE;

@zoecarver
Copy link
Contributor Author

@swift-ci please test

lib/ClangImporter/ImportDecl.cpp Outdated Show resolved Hide resolved
if (ctor->isDeleted())
return nullptr;
}
if (ctor->getAccess() != clang::AS_public) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True. However, I think we have to still check for public access level, since it would be otherwise quite weird that Swift can use a private constructor -- even if it is trivial.

@@ -54,17 +54,17 @@ func pass(s: StructWithSubobjectDefaultedCopyConstructor) {

// CHECK-LABEL: sil hidden [ossa] @$s4main4pass{{.*[ (]}}StructWithPrivateDefaultedCopyConstructor)
func pass(s: StructWithPrivateDefaultedCopyConstructor) {
// CHECK: bb0(%0 : $*StructWithPrivateDefaultedCopyConstructor):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. However, you added this code:

            if (ctor->isDeleted() || ctor->getAccess() != clang::AS_public)
              return nullptr;

shouldn't it take care of rejecting it based on the access control level?

}

// CHECK-LABEL: sil hidden [ossa] @$s4main4pass{{.*[ (]}}StructWithInheritedPrivateDefaultedCopyConstructor)
func pass(s: StructWithInheritedPrivateDefaultedCopyConstructor) {
// CHECK: bb0(%0 : $*StructWithInheritedPrivateDefaultedCopyConstructor):
// CHECK: bb0(%0 : $StructWithInheritedPrivateDefaultedCopyConstructor):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's code in the importer that should make a type address-only if there is any private constructor, do you know why that is not kicking in?

@@ -18,7 +18,7 @@
// This definition is a placeholder for importing into Swift.
// It provides size and alignment but cannot be manipulated safely there.
typedef struct {
__swift_uintptr_t refCounts SWIFT_ATTRIBUTE_UNAVAILABLE;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, in C-mode this attribute is meaningless which is why we didn't see the error before now.

Which error?

test/Interop/Cxx/value-witness-table/Inputs/basic.h Outdated Show resolved Hide resolved
test/Interop/Cxx/value-witness-table/copy-basic.swift Outdated Show resolved Hide resolved
test/Interop/Cxx/class/copy-constructor-abi.swift Outdated Show resolved Hide resolved
lib/IRGen/GenStruct.cpp Outdated Show resolved Hide resolved
@zoecarver
Copy link
Contributor Author

@swift-ci please smoke test.

@zoecarver
Copy link
Contributor Author

@swift-ci please smoke test macOS.

@zoecarver
Copy link
Contributor Author

@swift-ci please smoke test Windows.

@zoecarver
Copy link
Contributor Author

@swift-ci please smoke test Windows.

@zoecarver
Copy link
Contributor Author

@swift-ci please smoke test.

@zoecarver
Copy link
Contributor Author

@swift-ci please smoke test Windows.

@zoecarver
Copy link
Contributor Author

@swift-ci please smoke test.

@zoecarver
Copy link
Contributor Author

@gribozavr tests are passing and suggestions have been applied. Any more comments?

If a user-defined copy constructor exists, use that to copy imported C++
types.
@zoecarver
Copy link
Contributor Author

@swift-ci please test.

@zoecarver
Copy link
Contributor Author

@swift-ci please test Windows platform.

@zoecarver zoecarver merged commit 74b7341 into swiftlang:main Feb 4, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c++ interop Feature: Interoperability with C++
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants