Skip to content

[cxx-interop] Fix calling convention for rvalue reference params #79576

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

Merged
merged 1 commit into from
Mar 4, 2025

Conversation

Xazax-hun
Copy link
Contributor

In C++, we always expected to invoke the dtor for moved-from objects. This is not the case for swift. Fortunately, @incxx calling convention is already expressing that the caller supposed to destroy the object. This fixes the missing dtor calls when calling C++ functions taking rvalue references. Fixes #77894.

rdar://140786022

@Xazax-hun Xazax-hun added the c++ interop Feature: Interoperability with C++ label Feb 24, 2025
@Xazax-hun Xazax-hun force-pushed the gaborh/fix-consume-calling-conv branch from 33778fa to 0195d08 Compare February 25, 2025 18:30
@Xazax-hun Xazax-hun requested a review from eeckstein as a code owner February 25, 2025 18:30
@Xazax-hun Xazax-hun force-pushed the gaborh/fix-consume-calling-conv branch from 0195d08 to 3e2bf78 Compare February 26, 2025 12:26
Copy link
Contributor

@j-hui j-hui left a comment

Choose a reason for hiding this comment

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

I left some questions/suggestions for the test, but LGTM overall

func main() {
let x = MoveOnly()
// CHECK: MoveOnly 0 created
// CHECK-OPT: MoveOnly 0 created
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you document why this CHECK-OPT needs to exist? Is it because of potentially optimized temporaries?

If that's the case, I'm wondering whether it might be worth also testing this with -fno-elide-constructors, to ensure those temporaries are forcibly created.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh. I just looked at the // RUN directives and realized that OPT stands for "optimized" and not "optional". (And I thought that CHECK-OPT was built into LLVM-lit, rather than being user-defined.)

Disregard my question about what CHECK-OPT means, but I'm still wondering about -fno-elide-constructors, and I also wonder if there's a better check-prefix like CHECK-ONONE vs CHECK-DASHO that will make it obvious what is being tested.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated the prefixes. -fno-elide-constructors is a Clang flag right? Here, the copies are created within Swift, so I would not expect it to make any difference. I think the main differences between optimized and non-optimized code is coming from the optimized version inlining the function defined in Swift so we no longer need some of the copies.

func consume(_ x: consuming MoveOnly) {}
func consume(_ x: consuming Copyable) {}

func main() {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: can you break this test up into separate functions, and briefly document the expected behavior? It's a bit hard to keep track of which variables are allocated where.

Copy link
Contributor

Choose a reason for hiding this comment

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

breaking it up into separate functions should also make it clearer which Copyable instance is being destroyed at the end.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I split it up in 4.

struct Copyable {
int id;
Copyable() : id(0) { printf("Copyable %d created\n", id); }
Copyable(const Copyable& other) : id(other.id + 1) { printf("Copyable %d copy-created\n", id); }
Copy link
Contributor

Choose a reason for hiding this comment

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

Is/can this copy constructor invoked when we do something like:

let c0 = Copyable()
let c1 = Copyable(c0) // or should it be Copyable(other: c0)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, we do not import the copy constructor as an initializer into Swift. Users cannot invoke them directly.

@@ -3,6 +3,11 @@ module Reference {
requires cplusplus
}

module SpecialMembers {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: why is this module case called SpecialMembers/print-special-members.h? The theme seems to be about logging copy- and move-constructor calls.

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 C++ we call move/copy constructors special member functions: https://en.wikipedia.org/wiki/Special_member_functions

@@ -341,6 +341,7 @@ class AnalyzeForwardUse
case SILArgumentConvention::Indirect_In:
return true;
case SILArgumentConvention::Indirect_In_Guaranteed:
case SILArgumentConvention::Indirect_In_CXX:
Copy link
Contributor

Choose a reason for hiding this comment

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

Just to help me better understand this patch, because I don't see this case being defined in this patch, and the default case is llvm_unreachable.

Does this mean that the Indirect_In_Cxx case was not possible prior to this patch? (I'm guessing it's the additions to lib/SIL/IR/SILFunctionType.cpp that make this case possible?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I hit the unreachable assertion with the test case I added. My suspicion is that it was possible to trigger this even before my patch we just did not run into it with an assertions enabled compiler.

In C++, we always expected to invoke the dtor for moved-from objects.
This is not the case for swift. Fortunately, @incxx calling convention
is already expressing that the caller supposed to destroy the object.
This fixes the missing dtor calls when calling C++ functions taking
rvalue references. Fixes #77894.

rdar://140786022
@Xazax-hun Xazax-hun force-pushed the gaborh/fix-consume-calling-conv branch from 3e2bf78 to 00fa738 Compare March 3, 2025 11:47
@Xazax-hun
Copy link
Contributor Author

@swift-ci please smoke test

@Xazax-hun
Copy link
Contributor Author

@swift-ci please smoke test macOS

@Xazax-hun
Copy link
Contributor Author

@swift-ci please smoke test Linux

@Xazax-hun Xazax-hun merged commit c91e295 into main Mar 4, 2025
3 checks passed
@Xazax-hun Xazax-hun deleted the gaborh/fix-consume-calling-conv branch March 4, 2025 10:31
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.

C++ foreign type's dtor needs to be invoked on moved-from objects
2 participants