Skip to content

Add KnownLayout::MaybeUninit type #1822

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

Closed
wants to merge 1 commit into from
Closed

Add KnownLayout::MaybeUninit type #1822

wants to merge 1 commit into from

Conversation

joshlf
Copy link
Member

@joshlf joshlf commented Oct 5, 2024

Closes #1797

@joshlf joshlf requested a review from jswrenn October 5, 2024 02:47
build.rs Outdated
@@ -83,6 +83,7 @@ fn main() {
println!(
"cargo:rustc-check-cfg=cfg(__ZEROCOPY_INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS)"
);
println!("cargo:rustc-check-cfg=cfg(zerocopy_unstable)");
Copy link
Member Author

Choose a reason for hiding this comment

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

Remove this now that we're targeting 0.9?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I kept this PR targeting 0.8, since — as a rule — I think it's easier to develop for 0.8 and remove MSRV cruft in the cherrypick to 0.9 — than it is to go the other way around. The zerocopy_unstable CFGs are nonetheless removed, since I've instead doc(hidden)'d the MaybeUninit machinery.

src/lib.rs Outdated
Comment on lines 732 to 735
/// - `Self` and `Self::MaybeUninit` have the same:
/// - Fixed prefix size
/// - Alignment
/// - (For DSTs) trailing slice element size
Copy link
Member Author

Choose a reason for hiding this comment

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

You can make this more concise by saying that Self::LAYOUT and Self::MaybeUninit::LAYOUT are identical.

src/lib.rs Outdated
/// - It is valid to perform an `as` cast from `*mut Self` to `*mut
/// Self::MaybeUninit`, and this operation preserves referent size (ie,
/// `size_of_val_raw`).
/// - All bytes of `Self::MaybeUninit` may be uninitialized.
Copy link
Member Author

Choose a reason for hiding this comment

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

Maybe "Self::MaybeUninit has no bit validity constraints - any byte sequence is permitted, including uninitialized bytes"? Importantly, it's not just that uninit is valid, but all other (init) bytes are valid as well.

src/lib.rs Outdated
Comment on lines 876 to 879
// SAFETY: `CoreMaybeUninit<T>` has the same size and alignment as `T`.
// Consequently, `[CoreMaybeUninit<T>]` has the same size and alignment as
// `[T]`, becuase `CoreMaybeUninit<T>` has the same size and alignment as
// `T` [1].
Copy link
Member Author

Choose a reason for hiding this comment

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

This wording feels redundant.

Also, two issues:

  • Can you cite the slice layout docs to prove that [T] and T have the same alignment?
  • I believe you need to be more careful about what "size" means here - it's actually a "for all values" property
  • What about pointer casts?
  • What about bit validity?

This may get easier to write if you just define safety in terms of LAYOUT as I suggested above.

src/lib.rs Outdated
Comment on lines 973 to 990
/// SAFETY:
/// - By invariant on `KnownLayout::MaybeUninit`, `T` and `T::MaybeUninit`
/// have the same:
/// - Fixed prefix size
/// - Alignment
/// - (For DSTs) trailing slice element size
/// - By invariant on `KnownLayout`, it is valid to perform an `as` cast
/// from `*mut T` and `*mut T::MaybeUninit`, and this operation preserves
/// referent size (ie, `size_of_val_raw`).
Copy link
Member Author

Choose a reason for hiding this comment

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

Make sure to update this if you update the safety requirements on type MaybeUninit

Copy link
Collaborator

Choose a reason for hiding this comment

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

unsafe_impl_known_layout requires:

/// Implements `KnownLayout` for a type in terms of the implementation of
/// another type with the same representation.
///
/// # Safety
///
/// - `$ty` and `$repr` must have the same:
///   - Fixed prefix size
///   - Alignment
///   - (For DSTs) trailing slice element size
/// - It must be valid to perform an `as` cast from `*mut $repr` to `*mut $ty`,
///   and this operation must preserve referent size (ie, `size_of_val_raw`).
macro_rules! unsafe_impl_known_layout {

Does that final bullet point always follow from $ty and $repr having the same KnownLayout::LAYOUT, or is there more to prove?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah I think that follows, but you do need to update the comment to explicitly state that the ::LAYOUT equality is the premise you're using for this proof.

src/wrappers.rs Outdated

/// Creates a `Box<MaybeUninit<T>>`.
///
/// This function is useful for allocating large, uninit values on the heap,
Copy link
Member Author

Choose a reason for hiding this comment

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

Suggested change
/// This function is useful for allocating large, uninit values on the heap,
/// This function is useful for allocating large, uninit values on the heap

/// never to cause a panic or an abort.
#[cfg(feature = "alloc")]
#[inline]
pub fn new_boxed_uninit(meta: T::PointerMetadata) -> Result<Box<Self>, AllocError> {
Copy link
Member Author

Choose a reason for hiding this comment

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

Can we refactor so this isn't duplicated from FromZeros::new_boxed?

#[inline(always)]
pub const unsafe fn assume_init(self) -> T
where
T: Sized + KnownLayout<PointerMetadata = ()>,
Copy link
Member Author

Choose a reason for hiding this comment

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

Suggested change
T: Sized + KnownLayout<PointerMetadata = ()>,
T: Sized,

src/wrappers.rs Outdated
Comment on lines 631 to 556
/// The caller must ensure that `self` is in an initialized state. Depending
/// on subsequent use, it may also need to be in a valid state.
Copy link
Member Author

Choose a reason for hiding this comment

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

Let's be careful about bit validity vs safety here. It does need to be in a bit-valid state, but it is okay to not uphold (non-bit validity) safety invariants. Same in the safety comment in the body.

Comment on lines 169 to 171
"--cfg __ZEROCOPY_INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS --cfg zerocopy_derive_union_into_bytes "
"--cfg __ZEROCOPY_INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS --cfg zerocopy_derive_union_into_bytes --cfg zerocopy_unstable "
} else {
"--cfg zerocopy_derive_union_into_bytes "
"--cfg zerocopy_derive_union_into_bytes --cfg zerocopy_unstable "
Copy link
Member Author

Choose a reason for hiding this comment

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

Obsolete; remove these

Comment on lines 166 to 180
// The identifiers of the parameters without trait bounds or type defaults.
let param_idents = ast.generics.params.iter().map(|param| match param {
GenericParam::Type(ty) => {
let ident = &ty.ident;
quote!(#ident)
}
GenericParam::Lifetime(l) => {
let ident = &l.lifetime;
quote!(#ident)
}
GenericParam::Const(cnst) => {
let ident = &cnst.ident;
quote!({#ident})
}
});
Copy link
Member Author

Choose a reason for hiding this comment

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

This is duplicated from impl_block below just as a hack. We should refactor so we don't need to duplicate before merging.

Comment on lines 1432 to 1499
let outer_extras = if let Some(repr) = known_layout_maybe_uninit_struct_repr {
// PANICS: TODO
let (trailing_field, leading_fields) = fields.split_last().unwrap();
let (_name, trailing_field_ty) = trailing_field;
let leading_fields_tys = leading_fields.iter().map(|(_name, ty)| ty);
let params = params.clone();
let bounds = bounds.clone();
quote! {
#repr
#[doc(hidden)]
pub struct __ZerocopyKnownLayoutMaybeUninit<#(#params),*>(
#(
::zerocopy::util::macro_util::core_reexport::mem::MaybeUninit<#leading_fields_tys>,
)*
<#trailing_field_ty as ::zerocopy::KnownLayout>::MaybeUninit
) where #(#bounds,)*;
}
} else {
quote! {}
};
Copy link
Member Author

Choose a reason for hiding this comment

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

This is all a hack - this should live in fn derive_known_layout. It repeats a lot of the logic from that function.

pub fn new(val: T) -> Self
where
T: Sized + KnownLayout<PointerMetadata = ()>,
Self: Sized,
Copy link
Member Author

Choose a reason for hiding this comment

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

Before we merge this, let's try to make these bounds unnecessary or track somewhere that we want to eventually get rid of them. This will make it painful to use in a generic context.

@jswrenn jswrenn force-pushed the maybe-uninit branch 3 times, most recently from d94e5f7 to cbae3d7 Compare November 7, 2024 16:31
@jswrenn jswrenn changed the base branch from main to v0.8.x November 7, 2024 16:37
@jswrenn jswrenn force-pushed the maybe-uninit branch 11 times, most recently from dfa7e1e to e771b26 Compare November 13, 2024 19:34
This is achieved by adding a `MaybeUninit` associated type to
`KnownLayout`, whose layout is identical to `Self` except that it admits
uninitialized bytes in all positions.

For sized types, this is bound to `mem::MaybeUninit<Self>`. For
potentially unsized structs, we synthesize a doppelganger with the same
`repr`, whose leading fields are wrapped in `mem::MaybeUninit` and whose
trailing field is the `MaybeUninit` associated type of struct's original
trailing field type. This type-level recursion bottoms out at `[T]`,
whose `MaybeUninit` associated type is bound to `[mem::MaybeUninit<T>]`.

Makes progress towards #1797
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.

Support MaybeUninit<T: ?Sized> via KnownLayout
2 participants