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

Add support for calling C++ constructors #30630

Merged
merged 33 commits into from Oct 20, 2020

Conversation

martinboehme
Copy link
Collaborator

@martinboehme martinboehme commented Mar 25, 2020

A problem that we have to deal with when calling C++ constructors is that the ABI of a C++ constructor depends not only on the signature of the constructor but on the class on which it is defined. For example, it can be necessary to pass additional “implicit” arguments to constructors of virtual base classes.

It is therefore fundamentally not possible for SignatureExpansion (which doesn’t have access to the CXXConstructorDecl) to reliably produce the correct LLVM signature from only the type of the constructor. The approach we take is to try to get most of the way in SignatureExpansion by marking the SIL function return type with the @out attribute, representing the fact that C++ constructors take a this points to the object to be initialized.

This produces an “assumed” constructor ABI in SignatureExpansion that assumes there are no implicit arguments and that the return type of the constructor ABI is void (and indeed there is no way to represent anything else in the SIL type). If this assumed ABI doesn’t match the actual ABI, we insert a thunk in IRGen.

The thunk is marked alwaysinline, so it doesn’t incur any runtime overhead, but some compile-time overhead is required for LLVM to inline the thunk. If this turns out to be excessive, we can introduce what is essentially a peephole optimization for a full apply of a function_refthat refers to a C++ constructor and directly emit the constructor call in this case (along with any required implicit arguments) instead of calling the thunk. I’ve chosen not to tackle that in this PR though as it’s already large enough.

On some ABIs (e.g. Itanium x64), we get lucky and the ABI for a complete constructor call always matches the assumed ABI.

In addition to the core C++ constructor functionality, this PR adds some a couple of small required features:

  • Conversion of thin to a Clang void type
  • Handling of formal indirect results in SignatureExpansion::expandExternalSignatureTypes()

@martinboehme martinboehme marked this pull request as draft April 21, 2020 07:54
@gribozavr gribozavr added the c++ interop Feature: Interoperability with C++ label Apr 24, 2020
lib/ClangImporter/ImportName.cpp Outdated Show resolved Hide resolved
test/Interop/Cxx/class/Inputs/module.modulemap Outdated Show resolved Hide resolved
gribozavr pushed a commit to llvm/llvm-project that referenced this pull request May 19, 2020
Summary:
This is needed in Swift for C++ interop -- see here for the corresponding Swift change:

apple/swift#30630

As part of this change, I've had to make some changes to the interface of CGCXXABI to return the additional parameters separately rather than adding them directly to a `CallArgList`.

Reviewers: rjmccall

Reviewed By: rjmccall

Subscribers: cfe-commits

Tags: #clang

Differential Revision: https://reviews.llvm.org/D79942
martinboehme added a commit to martinboehme/llvm-project that referenced this pull request May 19, 2020
Summary:
This is needed in Swift for C++ interop -- see here for the corresponding Swift change:

apple/swift#30630

As part of this change, I've had to make some changes to the interface of CGCXXABI to return the additional parameters separately rather than adding them directly to a `CallArgList`.

Reviewers: rjmccall

Reviewed By: rjmccall

Subscribers: cfe-commits

Tags: #clang

Differential Revision: https://reviews.llvm.org/D79942
@martinboehme martinboehme changed the title Add support for calling C++ constructors [DO NOT MERGE] Add support for calling C++ constructors May 20, 2020
@martinboehme martinboehme marked this pull request as ready for review May 20, 2020 06:05
@martinboehme
Copy link
Collaborator Author

@swift-ci Please test

@martinboehme
Copy link
Collaborator Author

@swift-ci Please test Windows

@martinboehme
Copy link
Collaborator Author

This is now ready for review again -- sorry for the delay.

See also the discussion here:

https://forums.swift.org/t/calling-c-constructors-looking-for-feedback-on-my-approach/34787/18?u=mboehme

@martinboehme
Copy link
Collaborator Author

@rjmccall Not sure if you want to review this also?

@swift-ci
Copy link
Collaborator

Build failed
Swift Test OS X Platform
Git Sha - c8cd2d8b6a4b0f38c10cff1d9541c7b0a49202ef

// Note `this` return type and implicit "most derived" argument.
// MICROSOFT_X64: call %struct.HasVirtualBase* @"??0HasVirtualBase@@QEAA@UArgType@@@Z"(%struct.HasVirtualBase* %{{[0-9]+}}, i32 %{{[0-9]+}}, i32 1)
return HasVirtualBase(ArgType())
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you add some more tests, for cases that don't involve virtual base classes?

For example, you mentioned that constructors on some Itanium ABI variants on ARM64 iOS return 'this', so they would also need a thunk.

Also would be nice to have some tests for cases that don't need thunks.

Feel free to add some of them here if you are not using standard library types -- or in a separate file if you need standard library types.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Could you add some more tests, for cases that don't involve virtual base classes?

Done.

For example, you mentioned that constructors on some Itanium ABI variants on ARM64 iOS return 'this', so they would also need a thunk.

Some of the existing test cases already demonstrate this -- see the comments on the ITANIUM_ARM and MICROSOFT_X64 tests, which point out the this return type.

Also would be nice to have some tests for cases that don't need thunks.

This was already the case for the existing ITANIUM_X64 test. The newly added ITANIUM_X64 test also does not need a thunk.

// attributes of the constructor that was generated by Clang, which doesn't
// insert these attributes.
//
// - The `this` parameter should _not_ carry a `nocapture` attribute (unlike
Copy link
Collaborator

@gribozavr gribozavr May 20, 2020

Choose a reason for hiding this comment

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

If the type is loadable in Swift, nocapture is permitted, I think.

In this example, the function returns the result indirectly, and the result is marked nocapture. Since we construct a C++ object in that memory, the constructor must also be nocapture.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Hm... good point.

Since there is no way in general to guarantee that the C++ constructor will be nocapture, I think the conclusion here is that when we're returning C++ objects indirectly, we should always do so without nocapture?

(We could, in principle, add the nocapture back if we can prove to ourselves that the pointer can't be captured. But I think Swift currently isn't set up to do that -- I assume the place that adds the nocapture is in SignatureExpansion, and that only sees the type of the function, not its definition.)

Copy link
Member

Choose a reason for hiding this comment

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

Well, we can probably check whether the type is trivially copyable. If it's trivially copyable, it can't be captured, because Swift is absolutely not going to promise not to move it around.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think I see what you're saying. Swift can certainly also move non-trivially-copyable C++ types around as it pleases, but then the move constructor will notice this happening and can adjust the pointer that was previously captured by the constructor. But a trivially copyable type has no way of doing this, and so we can assume that the constructor doesn't capture this. Is this what you're getting at?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

FWIW, I've checked, and Clang doesn't add nocapture to the this parameter of a constructor, even if the type is trivially copyable. I'm not sure why this is, but if Clang isn't adding the nocapture, I don't think we should be adding it either.

There's a wider discussion of what we should be doing WRT to nocapture for Swift functions that return C++ types (see https://bugs.swift.org/browse/SR-12845), but that's outside the scope of this PR.

@martinboehme
Copy link
Collaborator Author

@swift-ci Please test

@swift-ci
Copy link
Collaborator

Build failed
Swift Test Linux Platform
Git Sha - c8cd2d8b6a4b0f38c10cff1d9541c7b0a49202ef

@swift-ci
Copy link
Collaborator

Build failed
Swift Test OS X Platform
Git Sha - c8cd2d8b6a4b0f38c10cff1d9541c7b0a49202ef

@martinboehme
Copy link
Collaborator Author

@swift-ci Please test

@swift-ci
Copy link
Collaborator

Build failed
Swift Test Linux Platform
Git Sha - b0b13db62f22a0b70b660b384b6148a363e1a55d

@swift-ci
Copy link
Collaborator

Build failed
Swift Test OS X Platform
Git Sha - b0b13db62f22a0b70b660b384b6148a363e1a55d

@martinboehme
Copy link
Collaborator Author

I've tried to reproduce the LLDB test failures locally, but they pass, even with ASAN enabled. Restarting testing in case this was just an infrastructure failure (though I'm not exactly sure how it could be).

@zoecarver
Copy link
Collaborator

It looks like (on Windows only?) it's possible for a function template to have a CXXConstructorName if it wraps a CXXConstructorDecl. I wonder if #33053 will fix this (I doubt it). But, if that doesn't work, I think a fairly simple fix would be to just get the constructor decl out of the function template if we find a function template in that branch.

@zoecarver
Copy link
Collaborator

#33053
@swift-ci please test Windows platform

@zoecarver
Copy link
Collaborator

Yeah, that didn't work. I'll try my other idea.

…orting the function name.

Sometimes, on windows, we get a function template wrapping the
constructor decl. In this case, look through the function template to
find the constructor decl.
@zoecarver
Copy link
Collaborator

@swift-ci please test Windows platform.

1 similar comment
@zoecarver
Copy link
Collaborator

@swift-ci please test Windows platform.

@zoecarver
Copy link
Collaborator

@rjmccall thank you again for the help reviewing. I really appreciate your insights. Any other comments?

Hoping to land this in the next few days.

@zoecarver
Copy link
Collaborator

Unless anyone has an objection, I'm going to land this patch on Wednesday.

@lattner
Copy link
Collaborator

lattner commented Oct 19, 2020

nice!

@rjmccall
Copy link
Member

It looks like (on Windows only?) it's possible for a function template to have a CXXConstructorName if it wraps a CXXConstructorDecl.

Oh, yes, that's right. I don't think that's Windows only — any constructor template should have that characteristic.

Copy link
Member

@rjmccall rjmccall left a comment

Choose a reason for hiding this comment

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

LGTM. Thanks for seeing this through! Very impressive work.

@zoecarver
Copy link
Collaborator

Thanks, @rjmccall. Given that you've both approved this PR, I'm going to go ahead and land it now.

Before it lands, I want to run the benchmarks, though. As much to test #32721 as anything else. Hopefully, this swift-ci incantation will work.

@zoecarver
Copy link
Collaborator

#32721
@swift-ci please benchmark.

@zoecarver
Copy link
Collaborator

Actually, thinking about it, that won't work because even if it does correctly merge the branches before benchmarking, the benchmark will be shown as "added" so, we won't be able to get any real data from it.

@swift-ci
Copy link
Collaborator

Performance: -O

Regression OLD NEW DELTA RATIO
FlattenListFlatMap 3743 5255 +40.4% 0.71x (?)
 
Improvement OLD NEW DELTA RATIO
StringBuilderWithLongSubstring 1830 1670 -8.7% 1.10x (?)

Code size: -O

Performance: -Osize

Improvement OLD NEW DELTA RATIO
NSError 318 229 -28.0% 1.39x (?)

Code size: -Osize

Performance: -Onone

Regression OLD NEW DELTA RATIO
String.data.Medium 190 207 +8.9% 0.92x (?)
DictionaryBridgeToObjC_BulkAccess 198 213 +7.6% 0.93x (?)

Code size: -swiftlibs

How to read the data The tables contain differences in performance which are larger than 8% and differences in code size which are larger than 1%.

If you see any unexpected regressions, you should consider fixing the
regressions before you merge the PR.

Noise: Sometimes the performance results (not code size!) contain false
alarms. Unexpected regressions which are marked with '(?)' are probably noise.
If you see regressions which you cannot explain you can try to run the
benchmarks again. If regressions still show up, please consult with the
performance team (@eeckstein).

Hardware Overview
  Model Name: Mac Pro
  Model Identifier: MacPro6,1
  Processor Name: 12-Core Intel Xeon E5
  Processor Speed: 2.7 GHz
  Number of Processors: 1
  Total Number of Cores: 12
  L2 Cache (per Core): 256 KB
  L3 Cache: 30 MB
  Memory: 64 GB

@zoecarver
Copy link
Collaborator

@swift-ci please test and merge.

1 similar comment
@zoecarver
Copy link
Collaborator

@swift-ci please test and merge.

@swift-ci swift-ci merged commit 7e16e3b into apple:main Oct 20, 2020
@hlopko
Copy link
Collaborator

hlopko commented Oct 25, 2020

Big congrats to both Martin and Zoe, impressive work!

zoecarver added a commit to zoecarver/swift that referenced this pull request Jan 31, 2021
This test was dissabled until apple#30630 (support calling C++ constructors)
landed. That patch landed a while ago, so this test should be enabled.
mem-frob pushed a commit to draperlaboratory/hope-llvm-project that referenced this pull request Oct 7, 2022
Summary:
This is needed in Swift for C++ interop -- see here for the corresponding Swift change:

apple/swift#30630

As part of this change, I've had to make some changes to the interface of CGCXXABI to return the additional parameters separately rather than adding them directly to a `CallArgList`.

Reviewers: rjmccall

Reviewed By: rjmccall

Subscribers: cfe-commits

Tags: #clang

Differential Revision: https://reviews.llvm.org/D79942
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.

None yet

10 participants