Implement ByteEq and ByteHash derives (#144)

* Implement `ByteEq` and `ByteHash` derives

This adds the derives `ByteEq` and `ByteHash` that can be used as an
alternative to the `Eq` / `PartialEq` and `Hash` derives from the
standard library. The difference is that these variants use `bytemuck`
to convert their values to byte slices before comparing / hashing them.
This allows the comparisons to turn into a simple `memcmp` / `bcmp` (or
completely inlined as a few vector instructions) and allows hashers to
process all bytes at once, possibly allowing for some vector operations
as well.

Here's a quick comparison of the generated assembly:
![https://i.imgur.com/CGTSWTZ.png](https://i.imgur.com/CGTSWTZ.png)

* Address review comments
This commit is contained in:
Christopher Serr 2022-11-17 21:13:24 +01:00 committed by GitHub
parent 6b1b7cecd3
commit 7311e9b4b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 91 additions and 4 deletions

View File

@ -224,6 +224,93 @@ pub fn derive_contiguous(
proc_macro::TokenStream::from(expanded) proc_macro::TokenStream::from(expanded)
} }
/// Derive the `PartialEq` and `Eq` trait for a type
///
/// The macro implements `PartialEq` and `Eq` by casting both sides of the
/// comparison to a byte slice and then compares those.
///
/// ## Warning
///
/// Since this implements a byte wise comparison, the behavior of floating point
/// numbers does not match their usual comparison behavior. Additionally other
/// custom comparison behaviors of the individual fields are also ignored. This
/// also does not implement `StructuralPartialEq` / `StructuralEq` like
/// `PartialEq` / `Eq` would. This means you can't pattern match on the values.
///
/// ## Example
///
/// ```rust
/// # use bytemuck_derive::{ByteEq, NoUninit};
/// #[derive(Copy, Clone, NoUninit, ByteEq)]
/// #[repr(C)]
/// struct Test {
/// a: u32,
/// b: char,
/// c: f32,
/// }
/// ```
#[proc_macro_derive(ByteEq)]
pub fn derive_byte_eq(
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let ident = input.ident;
proc_macro::TokenStream::from(quote! {
impl ::core::cmp::PartialEq for #ident {
#[inline]
#[must_use]
fn eq(&self, other: &Self) -> bool {
::bytemuck::bytes_of(self) == ::bytemuck::bytes_of(other)
}
}
impl ::core::cmp::Eq for #ident { }
})
}
/// Derive the `Hash` trait for a type
///
/// The macro implements `Hash` by casting the value to a byte slice and hashing
/// that.
///
/// ## Warning
///
/// The hash does not match the standard library's `Hash` derive.
///
/// ## Example
///
/// ```rust
/// # use bytemuck_derive::{ByteHash, NoUninit};
/// #[derive(Copy, Clone, NoUninit, ByteHash)]
/// #[repr(C)]
/// struct Test {
/// a: u32,
/// b: char,
/// c: f32,
/// }
/// ```
#[proc_macro_derive(ByteHash)]
pub fn derive_byte_hash(
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let ident = input.ident;
proc_macro::TokenStream::from(quote! {
impl ::core::hash::Hash for #ident {
#[inline]
fn hash<H: ::core::hash::Hasher>(&self, state: &mut H) {
::core::hash::Hash::hash_slice(::bytemuck::bytes_of(self), state)
}
#[inline]
fn hash_slice<H: ::core::hash::Hasher>(data: &[Self], state: &mut H) {
::core::hash::Hash::hash_slice(::bytemuck::cast_slice::<_, u8>(data), state)
}
}
})
}
/// Basic wrapper for error handling /// Basic wrapper for error handling
fn derive_marker_trait<Trait: Derivable>(input: DeriveInput) -> TokenStream { fn derive_marker_trait<Trait: Derivable>(input: DeriveInput) -> TokenStream {
derive_marker_trait_inner::<Trait>(input) derive_marker_trait_inner::<Trait>(input)

View File

@ -113,8 +113,8 @@ pub use transparent::*;
#[cfg(feature = "derive")] #[cfg(feature = "derive")]
pub use bytemuck_derive::{ pub use bytemuck_derive::{
AnyBitPattern, CheckedBitPattern, Contiguous, NoUninit, Pod, AnyBitPattern, ByteEq, ByteHash, CheckedBitPattern, Contiguous, NoUninit,
TransparentWrapper, Zeroable, Pod, TransparentWrapper, Zeroable,
}; };
/// The things that can go wrong when casting between [`Pod`] data forms. /// The things that can go wrong when casting between [`Pod`] data forms.

View File

@ -1,9 +1,9 @@
#![cfg(feature = "derive")] #![cfg(feature = "derive")]
#![allow(dead_code)] #![allow(dead_code)]
use bytemuck::{Pod, TransparentWrapper, Zeroable}; use bytemuck::{ByteEq, ByteHash, Pod, TransparentWrapper, Zeroable};
#[derive(Copy, Clone, Pod, Zeroable)] #[derive(Copy, Clone, Pod, Zeroable, ByteEq, ByteHash)]
#[repr(C)] #[repr(C)]
struct Test { struct Test {
a: u16, a: u16,