Skip to content

[cxx-interop] Fix metadata mismatch regarding fields of structs #81035

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
May 21, 2025

Conversation

susmonteiro
Copy link
Contributor

@susmonteiro susmonteiro commented Apr 23, 2025

In #78467 and #78961, we stopped emitting metadata for private C++ fields. However, this created a mismatch between the fields emitted and the number of fields + their offsets in the StructDescriptor.

rdar://147263490

@susmonteiro
Copy link
Contributor Author

@swift-ci please smoke test

@susmonteiro susmonteiro added the c++ interop Feature: Interoperability with C++ label Apr 23, 2025
@susmonteiro susmonteiro force-pushed the susmonteiro/metadata-private-fields branch from f36ff56 to a39010b Compare April 24, 2025 10:59
@susmonteiro
Copy link
Contributor Author

@swift-ci please smoke test

@susmonteiro susmonteiro marked this pull request as ready for review April 24, 2025 11:03
Copy link
Contributor

@Xazax-hun Xazax-hun left a comment

Choose a reason for hiding this comment

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

This is great, thanks for tracking this down! I am not super confident about some details, asked some questions inline. Maybe we should get someone more familiar with type metadata reviewed this PR.

@@ -1845,7 +1845,7 @@ namespace {

void addLayoutInfo() {
// uint32_t NumFields;
B.addInt32(getNumFields(getType()));
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if we need to do something similar for ClassContextDescriptorBuilder too.

Copy link
Contributor

Choose a reason for hiding this comment

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

I am less sure about emitInitializeFieldOffsetVector and co. Maybe those are good as is but maybe we want to ask someone familiar with type metadata.

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 wonder if we need to do something similar for ClassContextDescriptorBuilder too.

Yes I think you're right. I intend to check if we need a similar fix for classes and add those changes in a future PR

Regarding emitInitializeFieldOffsetVector, I'm also not sure. I'll investigate this further

Copy link
Contributor

Choose a reason for hiding this comment

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

emitInitializeFieldOffsetVector should include all fields. We are using that information to handle values in value witness functions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@drexin would it be a problem to have a discrepancy between the fields in emitInitializeFieldOffsetVector and the fields in StructContextDescriptorBuilder/FieldTypeMetadataBuilder?

Copy link
Contributor

Choose a reason for hiding this comment

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

Hm, I am not sure. cc: @mikeash

Copy link
Contributor

Choose a reason for hiding this comment

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

Reflection relies on the indexes in the field offset vector and the indexes of the descriptor's fields lining up. In theory you could get away with having additional entries at the end of the field offset vector, but not in the middle, and not more field records than field offsets.

@@ -64,7 +65,8 @@ template <class Impl> class StructMetadataVisitor
// Struct field offsets.
asImpl().noteStartOfFieldOffsets();
for (VarDecl *prop : Target->getStoredProperties())
asImpl().addFieldOffset(prop);
if (!isPrivateField(prop))
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if we can get out of sync with emitInitializeFieldOffsetVector here.

@susmonteiro susmonteiro force-pushed the susmonteiro/metadata-private-fields branch from a39010b to a49738e Compare April 24, 2025 14:49
@susmonteiro
Copy link
Contributor Author

@swift-ci please smoke test

@susmonteiro susmonteiro force-pushed the susmonteiro/metadata-private-fields branch from a49738e to abd2549 Compare April 25, 2025 11:52
@susmonteiro
Copy link
Contributor Author

@swift-ci please smoke test

// CHECK-linux-gnu: 42
// CHECK-linux-gnu: shared_ptr<CInt>()
printStdSharedPtrOfString()
// CHECK-macosx: basic_string<CChar, std.__1.char_traits<CChar>, std.__1.allocator<CChar>>()
Copy link
Contributor

Choose a reason for hiding this comment

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

Alternatively, you could add regexes that match on all platforms. I don't have a preference.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the suggestion!
Hopefully we will soon fix the conformance of std::string to CustomStringConvertible. Then we can remove all these os options, since the value of the string will be printed instead of the type name.
That said, I don't mind switching to regex

Copy link
Contributor

Choose a reason for hiding this comment

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

Good point, the string one is temporary (maybe not the shared_ptr one though). Let's leave it as is.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh right!

Copy link
Contributor

Choose a reason for hiding this comment

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

I think using regexes would be a bit more reliable here, since that would ensure the correct behavior on platforms that aren't listed here, e.g. Android or FreeBSD. The regex could be really permissive: something like {{.*}}.basic_string<CChar, {{.*}}> should do the trick.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, I'll make this change.

@susmonteiro susmonteiro force-pushed the susmonteiro/metadata-private-fields branch from abd2549 to 2a99a7d Compare April 25, 2025 15:25
@susmonteiro
Copy link
Contributor Author

@swift-ci please smoke test

Xazax-hun
Xazax-hun previously approved these changes Apr 25, 2025
// CHECK-linux-gnu: 42
// CHECK-linux-gnu: shared_ptr<CInt>()
printStdSharedPtrOfString()
// CHECK-macosx: basic_string<CChar, std.__1.char_traits<CChar>, std.__1.allocator<CChar>>()
Copy link
Contributor

Choose a reason for hiding this comment

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

Good point, the string one is temporary (maybe not the shared_ptr one though). Let's leave it as is.

@Xazax-hun Xazax-hun dismissed their stale review April 25, 2025 15:32

Misclicked.

@susmonteiro susmonteiro force-pushed the susmonteiro/metadata-private-fields branch from 2a99a7d to bdacdd0 Compare April 25, 2025 15:42
@susmonteiro
Copy link
Contributor Author

@swift-ci please smoke test

@susmonteiro
Copy link
Contributor Author

@swift-ci Please test macOS platform

@egorzhdan egorzhdan requested a review from tbkka May 1, 2025 14:37
Copy link
Contributor

@egorzhdan egorzhdan left a comment

Choose a reason for hiding this comment

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

This looks great, thanks!
I only have a couple of comments on testing.

// CHECK-linux-gnu: 42
// CHECK-linux-gnu: shared_ptr<CInt>()
printStdSharedPtrOfString()
// CHECK-macosx: basic_string<CChar, std.__1.char_traits<CChar>, std.__1.allocator<CChar>>()
Copy link
Contributor

Choose a reason for hiding this comment

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

I think using regexes would be a bit more reliable here, since that would ensure the correct behavior on platforms that aren't listed here, e.g. Android or FreeBSD. The regex could be really permissive: something like {{.*}}.basic_string<CChar, {{.*}}> should do the trick.

@susmonteiro susmonteiro force-pushed the susmonteiro/metadata-private-fields branch from bdacdd0 to 3efa921 Compare May 7, 2025 15:18
@susmonteiro
Copy link
Contributor Author

@swift-ci please smoke test

@susmonteiro susmonteiro force-pushed the susmonteiro/metadata-private-fields branch from 3efa921 to f21499c Compare May 14, 2025 15:44
@susmonteiro
Copy link
Contributor Author

@swift-ci please smoke test macOS platform

@susmonteiro
Copy link
Contributor Author

@swift-ci please smoke test

Copy link
Contributor

@drexin drexin left a comment

Choose a reason for hiding this comment

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

With the changes this looks good to me.


public:
HasPrivateFieldsOnly(int i, std::string s) : privInt(i), privStr(s) {}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

I recommend also testing:

  • some records with protected access (just to be thorough; these are also imported as private)
  • some structs with inherited fields, covering public/protected/private inheritance + using BaseClass::protected_field;
  • A computed property synthesized from SWIFT_COMPUTED_PROPERTY

Copy link
Contributor

Choose a reason for hiding this comment

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

You might be able to reuse some of the structs I defined in /Users/j-hui/Documents/open/swift/test/Interop/Cxx/class/access/Inputs/{non-public.h,non-public-inheritance.h,using-non-public.h} for testing, for the latter two I primarily focused on methods. If you throw in some fields you should be able to reuse the inheritance patterns I cover there.

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 added some tests for records with protected access. Regarding the rest of the tests you suggested:

some structs with inherited fields, covering public/protected/private inheritance + using BaseClass::protected_field;

Currently we don't print inherited fields. It's something we want to fix, but for now I decided not to add tests for this.

A computed property synthesized from SWIFT_COMPUTED_PROPERTY

These are also not getting printed, just like computed properties in Swift code.

#ifndef TEST_INTEROP_CXX_CLASS_INPUTS_SIMPLE_STRUCTS_H
#define TEST_INTEROP_CXX_CLASS_INPUTS_SIMPLE_STRUCTS_H

#include <string>
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: If we're trying to keep these structs simple in this test, maybe we should stick to primitive data types, and not have this test depend on the implementation of std::string? We should still absolutely test that but I think that should happen in a different test.

Copy link
Contributor

Choose a reason for hiding this comment

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

That said, do test nested printing, i.e., use a struct Inner that has a mix of public and private fields as the fields of another struct Outer, and try printing Outer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done!

Copy link
Contributor

Choose a reason for hiding this comment

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

nit: since you're no longer using std::string in this header file, you can probably delete this #include statement now 🙂


func printCxxStructPrivateAndPublicFields() {
let s = HasPrivateAndPublicFields(24, 42, "Hello", "World")
print(s)
Copy link
Contributor

Choose a reason for hiding this comment

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

Are there any other operations other than printing that relies on metadata? Perhaps we should test those too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure, I'm not very familiar with metadata. Do you have any operations in mind?

Copy link
Contributor

Choose a reason for hiding this comment

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

print covers it pretty well if the type doesn't conform to any of the pretty-printing protocols. You might also test _forEachFieldWithKeyPath.

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'll take a look at that, thanks!

@susmonteiro susmonteiro force-pushed the susmonteiro/metadata-private-fields branch from f21499c to 2b59c48 Compare May 19, 2025 13:06
@susmonteiro
Copy link
Contributor Author

@swift-ci please smoke test

@susmonteiro susmonteiro force-pushed the susmonteiro/metadata-private-fields branch from 2b59c48 to eca94bf Compare May 19, 2025 17:41
@susmonteiro
Copy link
Contributor Author

@swift-ci please smoke test

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.

This looks great, thanks for addressing my comments @susmonteiro ! There's just a few nits concerning the test setup, but those can be fixed in a follow-up if you'd like.

#ifndef TEST_INTEROP_CXX_CLASS_INPUTS_SIMPLE_STRUCTS_H
#define TEST_INTEROP_CXX_CLASS_INPUTS_SIMPLE_STRUCTS_H

#include <string>
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: since you're no longer using std::string in this header file, you can probably delete this #include statement now 🙂

@@ -0,0 +1,40 @@
// RUN: %empty-directory(%t)
// RUN: %target-build-swift %s -o %t/a.out -Xfrontend -enable-experimental-cxx-interop -I %S/Inputs
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// RUN: %target-build-swift %s -o %t/a.out -Xfrontend -enable-experimental-cxx-interop -I %S/Inputs
// RUN: %target-build-swift %s -o %t/a.out -Xfrontend -cxx-interoperability-mode=default -I %S/Inputs

@@ -0,0 +1,40 @@
// RUN: %empty-directory(%t)
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: is there any reason not to simplify this to something like?

// RUN: %target-run-simple-swift(-cxx-interoperability-mode=default -Xfrontend -disable-availability-checking -I %S/Inputs) | %FileCheck %s

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, there isn't. Thanks for the suggestion!

@@ -0,0 +1,90 @@
// RUN: %empty-directory(%t)
Copy link
Contributor

Choose a reason for hiding this comment

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

same question about using %target-run-simple-swift here

@@ -0,0 +1,90 @@
// RUN: %empty-directory(%t)
// RUN: %target-build-swift %s -o %t/a.out -Xfrontend -enable-experimental-cxx-interop -I %S/Inputs
Copy link
Contributor

Choose a reason for hiding this comment

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

nit:

Suggested change
// RUN: %target-build-swift %s -o %t/a.out -Xfrontend -enable-experimental-cxx-interop -I %S/Inputs
// RUN: %target-build-swift %s -o %t/a.out -Xfrontend -cxx-interoperability-mode=default -I %S/Inputs

@susmonteiro susmonteiro force-pushed the susmonteiro/metadata-private-fields branch from eca94bf to 72b13b3 Compare May 20, 2025 09:15
@susmonteiro
Copy link
Contributor Author

@swift-ci please smoke test

@susmonteiro susmonteiro enabled auto-merge May 20, 2025 09:19
@susmonteiro susmonteiro disabled auto-merge May 20, 2025 09:19
@susmonteiro susmonteiro enabled auto-merge May 20, 2025 09:57
@susmonteiro
Copy link
Contributor Author

@swift-ci please smoke test

@susmonteiro
Copy link
Contributor Author

@swift-ci please smoke test windows platform

@susmonteiro susmonteiro merged commit e805d93 into main May 21, 2025
3 checks passed
@susmonteiro susmonteiro deleted the susmonteiro/metadata-private-fields branch May 21, 2025 18:25
hamishknight added a commit to hamishknight/swift that referenced this pull request May 22, 2025
…/metadata-private-fields"

This reverts commit e805d93.
susmonteiro added a commit that referenced this pull request Jun 11, 2025
Adds support for printing a C++ foreign reference in Swift.

Also skips metadata of private fields in C++ records imported as Swift classes, following up on #81035

(cherry-picked from 848fad0)
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.

6 participants