Skip to content

[cxx-interop] Do not codegen zero-sized fields #79076

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
Feb 3, 2025

Conversation

Xazax-hun
Copy link
Contributor

Zero sized fields are messing up the offset calculations when we import C++ fields to Swift. We assume that the size of the field is determined by the type of the field. This is not true for fields marked with no_unique_address. Those fields can have 0 size while the sizeof(decltype(field)) is still 1.

rdar://143907490

@Xazax-hun Xazax-hun added the c++ interop Feature: Interoperability with C++ label Jan 31, 2025
@Xazax-hun Xazax-hun requested a review from j-hui January 31, 2025 16:00
@Xazax-hun
Copy link
Contributor Author

@swift-ci please smoke test

@Xazax-hun Xazax-hun changed the title [cxx-interop] Do not import zero-sized fields [cxx-interop] Do not codeine zero-sized fields Jan 31, 2025
@Xazax-hun Xazax-hun changed the title [cxx-interop] Do not codeine zero-sized fields [cxx-interop] Do not codegen zero-sized fields Jan 31, 2025
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 think the actual code change itself seems fine as is, but I think it might be a good idea to add a bit more testing coverage since this may be prone to some subtle runtime bugs.

In addition to what I've suggested, I'm wondering if it would be worth introducing some operations that might be sensitive to the size and layout of HasZeroSizedField, e.g.,

var s = HasZeroSizedField()

let t = s    // copy construction/assignment

cppFunc(s)   // pass to C++ function
swiftFunc(s) // pass to a Swift function

// getting and setting fields, which you're already testing

Finally, just to double-check: since this change is only in IRGen, it shouldn't affect anything in SILGen or before that, right? I'm wondering if it's worth adding tests to check the module interface, or to look at the SILGen/IRGen output.

Copy link
Contributor

@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.

We really shouldn't be doing any "offset calculations" here at all — the only correct information comes from Clang. And I wouldn't expect that we'd need to suppress just adding an entry for the field to the TypeInfo. I can imagine that we'd need to specifically suppress adding a field to the LLVM IR type, if we're duplicating that logic rather than just using Clang.

@Xazax-hun Xazax-hun force-pushed the gaborh/zero-sized-field-import branch from 3d285a9 to b136ec7 Compare February 3, 2025 11:55
Zero sized fields are messing up the offset calculations when we import
C++ fields to Swift. We assume that the size of the field is determined
by the type of the field. This is not true for fields marked with
no_unique_address. Those fields can have 0 size while the
sizeof(decltype(field)) is still 1.

rdar://143907490
@Xazax-hun Xazax-hun force-pushed the gaborh/zero-sized-field-import branch from b136ec7 to 998591e Compare February 3, 2025 11:56
@Xazax-hun
Copy link
Contributor Author

@swift-ci please smoke test

@Xazax-hun
Copy link
Contributor Author

I'm wondering if it would be worth introducing some operations that might be sensitive to the size and layout

Sure! I added more tests!

it shouldn't affect anything in SILGen or before that, right?

Yup!

adding tests to check the module interface, or to look at the SILGen/IRGen output.

I am a bit hesitant to do that. Checking the whole SIL would be really fragile without pointing out the source of the problem. I could check if the zero sized field is present but the accessibility of the zero sized field is already covered in the latest iteration of tests.

We really shouldn't be doing any "offset calculations" here at all — the only correct information comes from Clang.

We do use the offset information from Clang, but we also add padding on the Swift side manually to make sure the layout is the same and we use the Clang offsets to calculate these paddings. Unfortunately, this does not work for some zero sized types because Clang just returns zero as their offset (even if they are after some non-zero fields). Other reason to do offset calculations is how we handle inheritance. We don't have inheritance in Swift structs so we end up adding the bases' fields to the current type.

And I wouldn't expect that we'd need to suppress just adding an entry for the field to the TypeInfo.

I revised the patch to do what you suggest, keep adding the field to TypeInfo but avoid adding it to the LLVM IR type which matches what we do for other zero sized types like tail allocated C types.

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.

Looks good to me, thanks for addressing my comments!

@Xazax-hun Xazax-hun merged commit 40deafb into main Feb 3, 2025
3 checks passed
@Xazax-hun Xazax-hun deleted the gaborh/zero-sized-field-import branch February 3, 2025 17:22
auto isEmpty = isZeroSized || fieldType.isKnownEmpty(ResilienceExpansion::Maximal);
if (isEmpty) {
if (isZeroSized)
layout.completeEmpty(
Copy link
Contributor

@Michael137 Michael137 Apr 8, 2025

Choose a reason for hiding this comment

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

Don't have a lot of context about the interop side of things, but just wanted to point out that isZeroSized does not imply empty type (i.e., [[no_unique_address]] on non-empty fields allows Clang to fold other fields into the tail padding of that non-empty NUA field).

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah looks like Clang still doesn't currently make use of that (but it's a thing that could start happening, llvm/llvm-project#90462)

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, that is a good point. Once Clang starts to reuse the tail padding we definitely should have some tests for that. I do remember us running into some trouble when a base class' tail padding was reused, Egor did some fixes in that space.

Here, isZeroSized comes from FieldDecl::isZeroSize and it is documented as "Determine if this field is a subobject of zero size, that is, either a zero-length bit-field or a field of empty class type with the [[no_unique_address]] attribute.". I think based on this, it should be false for non-empty fields.

Copy link
Contributor

@Michael137 Michael137 Apr 9, 2025

Choose a reason for hiding this comment

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

One quirk is for example:

struct E1 {};
struct E2 { [[no_unique_address]] E1 e; };
struct Foo { [[no_unique_address]] E2 e; };

where isZeroSize on Foo::e would also be true (I think). Just brought it up in case it brings some edge-cases to mind :)

Once Clang starts to reuse the tail padding we definitely should have some tests for that.

Sounds good! 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, I see! Thanks a lot for the explanation! Now I see what you mean. I think this code path should be fine but this definitely worth having a test.

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.

4 participants