Skip to content

Conversation

jswrenn
Copy link
Collaborator

@jswrenn jswrenn commented Dec 3, 2024

This commit revises the KnownLayout derive on repr(C) target structs to preserve the correct resolution of Self when constructing __ZerocopyKnownLayoutMaybeUninit. This type contains a MaybeUninit version of each of the target struct's field types. Previously, it achieved this by simply copying the tokens corresponding to field types from the definition of the target struct into the definition of __ZerocopyKnownLayoutMaybeUninit

However, on types like this:

#[repr(C)]
struct Struct([u8; Self::N]);

…this approach is insufficient. Pasted into __ZerocopyKnownLayoutMaybeUninit, Self ceases to refer to the target struct and instead refers to __ZerocopyKnownLayoutMaybeUninit.

To preserve Self hygiene, this commit defines a struct for projecting the field types of the target struct based on their index:

pub unsafe trait Field<const N: usize> {
    type Type: ?Sized;
}

…then implements it for each of the field types of the target struct; e.g.:

impl Field<0> for Struct {
    type Type = [u8; Self::N];
}

With this, the fields of __ZerocopyKnownLayoutMaybeUninit can be defined hygienically; e.g., as <Struct as Field<0>>::Type.

Fixes #2116

@jswrenn jswrenn force-pushed the self-hygiene branch 5 times, most recently from d555cae to e3c5276 Compare December 3, 2024 22:10
@jswrenn
Copy link
Collaborator Author

jswrenn commented Dec 3, 2024

Unfortunately, with the added Field indirection in trailing_field_ty, the hack here to avoid an ICE on our MSRV no longer does so:

unsafe impl #impl_generics ::zerocopy::KnownLayout for __ZerocopyKnownLayoutMaybeUninit #ty_generics
where
#trailing_field_ty: ::zerocopy::KnownLayout,
// This bound may appear to be superfluous, but is required
// on our MSRV (1.55) to avoid an ICE.
<#trailing_field_ty as ::zerocopy::KnownLayout>::MaybeUninit: ::zerocopy::KnownLayout,
#predicates
{

This is the test case that exhibits the ICE:

#[derive(imp::KnownLayout)]
#[repr(C)]
struct WithParams<'a: 'b, 'b: 'a, T: 'a + 'b + imp::KnownLayout, const N: usize>(
[T; N],
imp::PhantomData<&'a &'b ()>,
)
where
'a: 'b,
'b: 'a,
T: 'a + 'b + imp::KnownLayout;

The problematic quality here is the lifetimes in the trailing field. Lifetimes in leading fields are unproblematic.

Not yet sure how to work around this.

@jswrenn jswrenn changed the title [wip] Practice good Self hygiene Practice good Self hygiene Dec 3, 2024
@joshlf
Copy link
Member

joshlf commented Dec 4, 2024

Unfortunately, with the added Field indirection in trailing_field_ty, the hack here to avoid an ICE on our MSRV no longer does so:

unsafe impl #impl_generics ::zerocopy::KnownLayout for __ZerocopyKnownLayoutMaybeUninit #ty_generics
where
#trailing_field_ty: ::zerocopy::KnownLayout,
// This bound may appear to be superfluous, but is required
// on our MSRV (1.55) to avoid an ICE.
<#trailing_field_ty as ::zerocopy::KnownLayout>::MaybeUninit: ::zerocopy::KnownLayout,
#predicates
{

This is the test case that exhibits the ICE:

#[derive(imp::KnownLayout)]
#[repr(C)]
struct WithParams<'a: 'b, 'b: 'a, T: 'a + 'b + imp::KnownLayout, const N: usize>(
[T; N],
imp::PhantomData<&'a &'b ()>,
)
where
'a: 'b,
'b: 'a,
T: 'a + 'b + imp::KnownLayout;

The problematic quality here is the lifetimes in the trailing field. Lifetimes in leading fields are unproblematic.

Not yet sure how to work around this.

Hot take: what if we don't work around it? The likelihood of someone encountering this in practice is extremely low since our MSRV is so old. If someone does, we can explain that it's a compiler bug and thus not really our fault.

I'd want to figure out what toolchain fixed this ICE. If it's a recent toolchain, then I wouldn't want to go this route, but if it's like 1.58 or something, then I'm okay with it.

@jswrenn
Copy link
Collaborator Author

jswrenn commented Dec 4, 2024

The last problematic rustc version is 1.61, released May 19, 2022.

@jswrenn
Copy link
Collaborator Author

jswrenn commented Dec 4, 2024

The problematic quality here is the lifetimes in the trailing field. Lifetimes in leading fields are unproblematic.

Static lifetimes are also unproblematic.

@jswrenn jswrenn force-pushed the self-hygiene branch 4 times, most recently from 87046ff to 2a40051 Compare December 4, 2024 18:23
@codecov-commenter
Copy link

codecov-commenter commented Dec 4, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 87.42%. Comparing base (7431cbd) to head (9f78722).
Report is 3 commits behind head on v0.8.x.

Additional details and impacted files
@@           Coverage Diff           @@
##           v0.8.x    #2127   +/-   ##
=======================================
  Coverage   87.42%   87.42%           
=======================================
  Files          16       16           
  Lines        6115     6115           
=======================================
  Hits         5346     5346           
  Misses        769      769           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@jswrenn jswrenn force-pushed the self-hygiene branch 5 times, most recently from b4e7382 to c7cfc8d Compare December 4, 2024 19:27
@jswrenn jswrenn requested a review from joshlf December 4, 2024 19:33
Comment on lines +319 to +328
#(::zerocopy::util::macro_util::core_reexport::mem::MaybeUninit<
<#ident #ty_generics as
::zerocopy::util::macro_util::Field<#leading_field_indices>
>::Type
>,)*
Copy link
Member

Choose a reason for hiding this comment

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

Comment here explaining why we indirect via Field rather than just using the field types directly?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

Also mention #2116 in this comment?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Copy link
Member

@joshlf joshlf left a comment

Choose a reason for hiding this comment

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

One more change requested, but otherwise LGTM.

This commit revises the `KnownLayout` derive on `repr(C)` target structs to
preserve the correct resolution of `Self` when constructing
`__ZerocopyKnownLayoutMaybeUninit`. This type contains a `MaybeUninit` version
of each of the target struct's field types. Previously, it achieved this by
simply copying the tokens corresponding to field types from the definition of
the target struct into the definition of `__ZerocopyKnownLayoutMaybeUninit`

However, on types like this:

    #[repr(C)]
    struct Struct([u8; Self::N]);

…this approach is insufficient. Pasted into `__ZerocopyKnownLayoutMaybeUninit`,
`Self` ceases to refer to the target struct and instead refers to
`__ZerocopyKnownLayoutMaybeUninit`.

To preserve `Self` hygiene, this commit defines a struct for projecting the
field types of the target struct based on their index:

    pub unsafe trait Field<Index> {
        type Type: ?Sized;
    }

…then implements it for each of the field types of the target struct; e.g.:

    struct Index<const N: usize>;

    impl Field<Index<0>> for Struct {
        type Type = [u8; Self::N];
    }

With this, the fields of `__ZerocopyKnownLayoutMaybeUninit` can be defined
hygienically; e.g., as `<Struct as Field<0>>::Type`.

Fixes #2116
@jswrenn jswrenn added this pull request to the merge queue Dec 4, 2024
Merged via the queue into v0.8.x with commit f436922 Dec 4, 2024
87 checks passed
@jswrenn jswrenn deleted the self-hygiene branch December 4, 2024 21:11
google-pr-creation-bot pushed a commit to google-pr-creation-bot/zerocopy that referenced this pull request Feb 6, 2025
This commit revises the `KnownLayout` derive on `repr(C)` target structs to
preserve the correct resolution of `Self` when constructing
`__ZerocopyKnownLayoutMaybeUninit`. This type contains a `MaybeUninit` version
of each of the target struct's field types. Previously, it achieved this by
simply copying the tokens corresponding to field types from the definition of
the target struct into the definition of `__ZerocopyKnownLayoutMaybeUninit`

However, on types like this:

    #[repr(C)]
    struct Struct([u8; Self::N]);

…this approach is insufficient. Pasted into `__ZerocopyKnownLayoutMaybeUninit`,
`Self` ceases to refer to the target struct and instead refers to
`__ZerocopyKnownLayoutMaybeUninit`.

To preserve `Self` hygiene, this commit defines a struct for projecting the
field types of the target struct based on their index:

    pub unsafe trait Field<Index> {
        type Type: ?Sized;
    }

…then implements it for each of the field types of the target struct; e.g.:

    struct Index<const N: usize>;

    impl Field<Index<0>> for Struct {
        type Type = [u8; Self::N];
    }

With this, the fields of `__ZerocopyKnownLayoutMaybeUninit` can be defined
hygienically; e.g., as `<Struct as Field<0>>::Type`.

Fixes google#2116
github-merge-queue bot pushed a commit that referenced this pull request Feb 6, 2025
This commit revises the `KnownLayout` derive on `repr(C)` target structs to
preserve the correct resolution of `Self` when constructing
`__ZerocopyKnownLayoutMaybeUninit`. This type contains a `MaybeUninit` version
of each of the target struct's field types. Previously, it achieved this by
simply copying the tokens corresponding to field types from the definition of
the target struct into the definition of `__ZerocopyKnownLayoutMaybeUninit`

However, on types like this:

    #[repr(C)]
    struct Struct([u8; Self::N]);

…this approach is insufficient. Pasted into `__ZerocopyKnownLayoutMaybeUninit`,
`Self` ceases to refer to the target struct and instead refers to
`__ZerocopyKnownLayoutMaybeUninit`.

To preserve `Self` hygiene, this commit defines a struct for projecting the
field types of the target struct based on their index:

    pub unsafe trait Field<Index> {
        type Type: ?Sized;
    }

…then implements it for each of the field types of the target struct; e.g.:

    struct Index<const N: usize>;

    impl Field<Index<0>> for Struct {
        type Type = [u8; Self::N];
    }

With this, the fields of `__ZerocopyKnownLayoutMaybeUninit` can be defined
hygienically; e.g., as `<Struct as Field<0>>::Type`.

Fixes #2116

Co-authored-by: Jack Wrenn <jswrenn@amazon.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants