Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/Lokathor/bytemuck into main
Browse files Browse the repository at this point in the history
  • Loading branch information
Lokathor committed Sep 5, 2023
2 parents 1661041 + 39b42b8 commit 763d69e
Show file tree
Hide file tree
Showing 10 changed files with 414 additions and 27 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
# note: the mips targets are here so that we have big-endian coverage (both 32bit and 64bit)
target: [i686-unknown-linux-gnu, mips-unknown-linux-gnu, mips64-unknown-linux-gnuabi64]
# we once had mips runners for Big-endian coverage but those got demoted to tier 3.
target: [i686-unknown-linux-gnu]
steps:
- uses: hecrj/setup-rust-action@v1
with:
Expand Down
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "bytemuck"
description = "A crate for mucking around with piles of bytes."
version = "1.13.1"
version = "1.13.2"
authors = ["Lokathor <[email protected]>"]
repository = "https://github.com/Lokathor/bytemuck"
readme = "README.md"
Expand All @@ -20,6 +20,7 @@ zeroable_maybe_uninit = []
zeroable_atomics = []
min_const_generics = []
wasm_simd = [] # Until >= 1.54.0 is MSRV this is an off-by-default feature.
must_cast = [] # Until >= 1.57.0 is MSRV this is an off-by-default feature.
aarch64_simd = [] # Until >= 1.59.0 is MSRV this is an off-by-default feature.

# Do not use if you can avoid it, because this is unsound.
Expand All @@ -42,6 +43,7 @@ features = [
"zeroable_atomics",
"min_const_generics",
"wasm_simd",
"must_cast",
]

[package.metadata.playground]
Expand All @@ -54,4 +56,5 @@ features = [
"zeroable_atomics",
"min_const_generics",
"wasm_simd",
"must_cast",
]
2 changes: 1 addition & 1 deletion derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ proc-macro = true
# syn seems to have broken backwards compatibility in this version https://github.com/dtolnay/syn/issues/1194
syn = "2.0.1"
quote = "1"
proc-macro2 = "1"
proc-macro2 = "1.0.60"

[dev-dependencies]
bytemuck = { path = "../", features = ["derive"] }
196 changes: 184 additions & 12 deletions derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,72 @@ pub fn derive_anybitpattern(
///
/// ```rust
/// # use bytemuck_derive::{Zeroable};
///
/// #[derive(Copy, Clone, Zeroable)]
/// #[repr(C)]
/// struct Test {
/// a: u16,
/// b: u16,
/// }
/// ```
#[proc_macro_derive(Zeroable)]
///
/// # Custom bounds
///
/// Custom bounds for the derived `Zeroable` impl can be given using the
/// `#[zeroable(bound = "")]` helper attribute.
///
/// Using this attribute additionally opts-in to "perfect derive" semantics,
/// where instead of adding bounds for each generic type parameter, bounds are
/// added for each field's type.
///
/// ## Examples
///
/// ```rust
/// # use bytemuck::Zeroable;
/// # use std::marker::PhantomData;
/// #[derive(Clone, Zeroable)]
/// #[zeroable(bound = "")]
/// struct AlwaysZeroable<T> {
/// a: PhantomData<T>,
/// }
///
/// AlwaysZeroable::<std::num::NonZeroU8>::zeroed();
/// ```
///
/// ```rust,compile_fail
/// # use bytemuck::Zeroable;
/// # use std::marker::PhantomData;
/// #[derive(Clone, Zeroable)]
/// #[zeroable(bound = "T: Copy")]
/// struct ZeroableWhenTIsCopy<T> {
/// a: PhantomData<T>,
/// }
///
/// ZeroableWhenTIsCopy::<String>::zeroed();
/// ```
///
/// The restriction that all fields must be Zeroable is still applied, and this
/// is enforced using the mentioned "perfect derive" semantics.
///
/// ```rust
/// # use bytemuck::Zeroable;
/// #[derive(Clone, Zeroable)]
/// #[zeroable(bound = "")]
/// struct ZeroableWhenTIsZeroable<T> {
/// a: T,
/// }
/// ZeroableWhenTIsZeroable::<u32>::zeroed();
/// ```
///
/// ```rust,compile_fail
/// # use bytemuck::Zeroable;
/// # #[derive(Clone, Zeroable)]
/// # #[zeroable(bound = "")]
/// # struct ZeroableWhenTIsZeroable<T> {
/// # a: T,
/// # }
/// ZeroableWhenTIsZeroable::<String>::zeroed();
/// ```
#[proc_macro_derive(Zeroable, attributes(zeroable))]
pub fn derive_zeroable(
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
Expand Down Expand Up @@ -317,12 +374,127 @@ fn derive_marker_trait<Trait: Derivable>(input: DeriveInput) -> TokenStream {
.unwrap_or_else(|err| err.into_compile_error())
}

/// Find `#[name(key = "value")]` helper attributes on the struct, and return
/// their `"value"`s parsed with `parser`.
///
/// Returns an error if any attributes with the given `name` do not match the
/// expected format. Returns `Ok([])` if no attributes with `name` are found.
fn find_and_parse_helper_attributes<P: syn::parse::Parser + Copy>(
attributes: &[syn::Attribute], name: &str, key: &str, parser: P,
example_value: &str, invalid_value_msg: &str,
) -> Result<Vec<P::Output>> {
let invalid_format_msg =
format!("{name} attribute must be `{name}({key} = \"{example_value}\")`",);
let values_to_check = attributes.iter().filter_map(|attr| match &attr.meta {
// If a `Path` matches our `name`, return an error, else ignore it.
// e.g. `#[zeroable]`
syn::Meta::Path(path) => path
.is_ident(name)
.then(|| Err(syn::Error::new_spanned(path, &invalid_format_msg))),
// If a `NameValue` matches our `name`, return an error, else ignore it.
// e.g. `#[zeroable = "hello"]`
syn::Meta::NameValue(namevalue) => {
namevalue.path.is_ident(name).then(|| {
Err(syn::Error::new_spanned(&namevalue.path, &invalid_format_msg))
})
}
// If a `List` matches our `name`, match its contents to our format, else
// ignore it. If its contents match our format, return the value, else
// return an error.
syn::Meta::List(list) => list.path.is_ident(name).then(|| {
let namevalue: syn::MetaNameValue = syn::parse2(list.tokens.clone())
.map_err(|_| {
syn::Error::new_spanned(&list.tokens, &invalid_format_msg)
})?;
if namevalue.path.is_ident(key) {
match namevalue.value {
syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(strlit), ..
}) => Ok(strlit),
_ => {
Err(syn::Error::new_spanned(&namevalue.path, &invalid_format_msg))
}
}
} else {
Err(syn::Error::new_spanned(&namevalue.path, &invalid_format_msg))
}
}),
});
// Parse each value found with the given parser, and return them if no errors
// occur.
values_to_check
.map(|lit| {
let lit = lit?;
lit.parse_with(parser).map_err(|err| {
syn::Error::new_spanned(&lit, format!("{invalid_value_msg}: {err}"))
})
})
.collect()
}

fn derive_marker_trait_inner<Trait: Derivable>(
mut input: DeriveInput,
) -> Result<TokenStream> {
// Enforce Pod on all generic fields.
let trait_ = Trait::ident(&input)?;
add_trait_marker(&mut input.generics, &trait_);
// If this trait allows explicit bounds, and any explicit bounds were given,
// then use those explicit bounds. Else, apply the default bounds (bound
// each generic type on this trait).
if let Some(name) = Trait::explicit_bounds_attribute_name() {
// See if any explicit bounds were given in attributes.
let explicit_bounds = find_and_parse_helper_attributes(
&input.attrs,
name,
"bound",
<syn::punctuated::Punctuated<syn::WherePredicate, syn::Token![,]>>::parse_terminated,
"Type: Trait",
"invalid where predicate",
)?;

if !explicit_bounds.is_empty() {
// Explicit bounds were given.
// Enforce explicitly given bounds, and emit "perfect derive" (i.e. add
// bounds for each field's type).
let explicit_bounds = explicit_bounds
.into_iter()
.flatten()
.collect::<Vec<syn::WherePredicate>>();

let predicates = &mut input.generics.make_where_clause().predicates;

predicates.extend(explicit_bounds);

let fields = match &input.data {
syn::Data::Struct(syn::DataStruct { fields, .. }) => fields.clone(),
syn::Data::Union(_) => {
return Err(syn::Error::new_spanned(
trait_,
&"perfect derive is not supported for unions",
));
}
syn::Data::Enum(_) => {
return Err(syn::Error::new_spanned(
trait_,
&"perfect derive is not supported for enums",
));
}
};

for field in fields {
let ty = field.ty;
predicates.push(syn::parse_quote!(
#ty: #trait_
));
}
} else {
// No explicit bounds were given.
// Enforce trait bound on all type generics.
add_trait_marker(&mut input.generics, &trait_);
}
} else {
// This trait does not allow explicit bounds.
// Enforce trait bound on all type generics.
add_trait_marker(&mut input.generics, &trait_);
}

let name = &input.ident;

Expand All @@ -339,11 +511,8 @@ fn derive_marker_trait_inner<Trait: Derivable>(
quote!()
};

let where_clause = if Trait::requires_where_clause() {
where_clause
} else {
None
};
let where_clause =
if Trait::requires_where_clause() { where_clause } else { None };

Ok(quote! {
#asserts
Expand All @@ -364,9 +533,12 @@ fn add_trait_marker(generics: &mut syn::Generics, trait_name: &syn::Path) {
let type_params = generics
.type_params()
.map(|param| &param.ident)
.map(|param| syn::parse_quote!(
#param: #trait_name
)).collect::<Vec<syn::WherePredicate>>();
.map(|param| {
syn::parse_quote!(
#param: #trait_name
)
})
.collect::<Vec<syn::WherePredicate>>();

generics.make_where_clause().predicates.extend(type_params);
}
11 changes: 10 additions & 1 deletion derive/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ pub trait Derivable {
fn requires_where_clause() -> bool {
true
}
fn explicit_bounds_attribute_name() -> Option<&'static str> {
None
}
}

pub struct Pod;
Expand Down Expand Up @@ -126,6 +129,10 @@ impl Derivable for Zeroable {
Data::Enum(_) => bail!("Deriving Zeroable is not supported for enums"),
}
}

fn explicit_bounds_attribute_name() -> Option<&'static str> {
Some("zeroable")
}
}

pub struct NoUninit;
Expand Down Expand Up @@ -532,12 +539,13 @@ fn generate_assert_no_padding(input: &DeriveInput) -> Result<TokenStream> {
let size_rest =
quote_spanned!(span => #( + ::core::mem::size_of::<#field_types>() )*);

quote_spanned!(span => #size_first#size_rest)
quote_spanned!(span => #size_first #size_rest)
} else {
quote_spanned!(span => 0)
};

Ok(quote_spanned! {span => const _: fn() = || {
#[doc(hidden)]
struct TypeWithoutPadding([u8; #size_sum]);
let _ = ::core::mem::transmute::<#struct_type, TypeWithoutPadding>;
};})
Expand All @@ -554,6 +562,7 @@ fn generate_fields_are_trait(
let field_types = get_field_types(&fields);
Ok(quote_spanned! {span => #(const _: fn() = || {
#[allow(clippy::missing_const_for_fn)]
#[doc(hidden)]
fn check #impl_generics () #where_clause {
fn assert_impl<T: #trait_>() {}
assert_impl::<#field_types>();
Expand Down
3 changes: 2 additions & 1 deletion src/allocation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -685,4 +685,5 @@ pub trait TransparentWrapperAlloc<Inner: ?Sized>:
}
}
}
impl<I: ?Sized, T: TransparentWrapper<I>> TransparentWrapperAlloc<I> for T {}

impl<I: ?Sized, T: ?Sized + TransparentWrapper<I>> TransparentWrapperAlloc<I> for T {}
10 changes: 2 additions & 8 deletions src/checked.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ unsafe impl CheckedBitPattern for bool {
}
}

// Rust 1.70.0 documents that NonZero[int] has the same layout as [int].
macro_rules! impl_checked_for_nonzero {
($($nonzero:ty: $primitive:ty),* $(,)?) => {
$(
Expand All @@ -178,14 +179,7 @@ macro_rules! impl_checked_for_nonzero {

#[inline]
fn is_valid_bit_pattern(bits: &Self::Bits) -> bool {
// Note(zachs18): The size and alignment check are almost certainly
// not necessary, but Rust currently doesn't explicitly document that
// NonZero[int] has the same layout as [int], so we check it to be safe.
// In a const to reduce debug-profile overhead.
const LAYOUT_SAME: bool =
core::mem::size_of::<$nonzero>() == core::mem::size_of::<$primitive>()
&& core::mem::align_of::<$nonzero>() == core::mem::align_of::<$primitive>();
LAYOUT_SAME && *bits != 0
*bits != 0
}
}
)*
Expand Down
5 changes: 5 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ pub use pod::*;
mod pod_in_option;
pub use pod_in_option::*;

#[cfg(feature = "must_cast")]
mod must;
#[cfg(feature = "must_cast")]
pub use must::*;

mod no_uninit;
pub use no_uninit::*;

Expand Down
Loading

0 comments on commit 763d69e

Please sign in to comment.