This provides a derive macro for `AsBytes` and `FromBytes` for structs
only. For both, it checks the respective trait on every underlying
field. For `AsBytes`, it emits a const-time padding check that will fail
the compilation if derived on a type with padding.
Signed-off-by: Matthew Maurer <mmaurer@google.com>
---
rust/macros/lib.rs | 63 ++++++++++++++++++++++++++++++++++++++++++++++++
rust/macros/transmute.rs | 58 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 121 insertions(+)
diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
index b38002151871a33f6b4efea70be2deb6ddad38e2..d66397942529f67697f74a908e257cacc4201d84 100644
--- a/rust/macros/lib.rs
+++ b/rust/macros/lib.rs
@@ -20,9 +20,14 @@
mod kunit;
mod module;
mod paste;
+mod transmute;
mod vtable;
use proc_macro::TokenStream;
+use syn::{
+ parse_macro_input,
+ DeriveInput, //
+};
/// Declares a kernel module.
///
@@ -475,3 +480,61 @@ pub fn paste(input: TokenStream) -> TokenStream {
pub fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
kunit::kunit_tests(attr, ts)
}
+
+/// Implements `FromBytes` for a struct.
+///
+/// It will fail compilation if the struct you are deriving on cannot be determined to implement
+/// `FromBytes` safely. It may still fail for some types which would be safe to implement
+/// `FromBytes` for, in which case you will need to write the implementation and justification
+/// yourself.
+///
+/// Main reasons your type may be rejected:
+/// * Not a `struct`
+/// * One of the fields is not `FromBytes`
+///
+/// # Examples
+///
+/// ```
+/// #[derive(FromBytes)]
+/// #[repr(C)]
+/// struct Foo {
+/// x: u32,
+/// y: u16,
+/// z: u16,
+/// }
+/// ```
+#[proc_macro_derive(FromBytes)]
+pub fn derive_from_bytes(tokens: TokenStream) -> TokenStream {
+ let input = parse_macro_input!(tokens as DeriveInput);
+ transmute::from_bytes(input).into()
+}
+
+/// Implements `AsBytes` for a struct.
+///
+/// It will fail compilation if the struct you are deriving on cannot be determined to implement
+/// `AsBytes` safely. It may still fail for some structures which would be safe to implement
+/// `AsBytes`, in which case you will need to write the implementation and justification
+/// yourself.
+///
+/// Main reasons your type may be rejected:
+/// * Not a `struct`
+/// * One of the fields is not `AsBytes`
+/// * Your struct has generic parameters
+/// * There is padding somewhere in your struct
+///
+/// # Examples
+///
+/// ```
+/// #[derive(AsBytes)]
+/// #[repr(C)]
+/// struct Foo {
+/// x: u32,
+/// y: u16,
+/// z: u16,
+/// }
+/// ```
+#[proc_macro_derive(AsBytes)]
+pub fn derive_as_bytes(tokens: TokenStream) -> TokenStream {
+ let input = parse_macro_input!(tokens as DeriveInput);
+ transmute::as_bytes(input).into()
+}
diff --git a/rust/macros/transmute.rs b/rust/macros/transmute.rs
new file mode 100644
index 0000000000000000000000000000000000000000..43cf36a1334f1fed23c0e777026392f987f78d8d
--- /dev/null
+++ b/rust/macros/transmute.rs
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-2.0
+
+use proc_macro2::TokenStream;
+use syn::{parse_quote, DeriveInput, Fields, Ident, ItemConst, Path, WhereClause};
+
+fn all_fields_impl(fields: &Fields, trait_: &Path) -> WhereClause {
+ let tys = fields.iter().map(|field| &field.ty);
+ parse_quote! {
+ where #(for<'a> #tys: #trait_),*
+ }
+}
+
+fn struct_padding_check(fields: &Fields, name: &Ident) -> ItemConst {
+ let tys = fields.iter().map(|field| &field.ty);
+ parse_quote! {
+ const _: () = {
+ assert!(#(core::mem::size_of::<#tys>())+* == core::mem::size_of::<#name>());
+ };
+ }
+}
+
+pub(crate) fn as_bytes(input: DeriveInput) -> TokenStream {
+ if !input.generics.params.is_empty() {
+ return quote::quote! { compile_error!("#[derive(AsBytes)] does not support generics") };
+ }
+ let syn::Data::Struct(ref ds) = &input.data else {
+ return quote::quote! { compile_error!("#[derive(AsBytes)] only supports structs") };
+ };
+ let name = input.ident;
+ let trait_ = parse_quote! { ::kernel::transmute::AsBytes };
+ let where_clause = all_fields_impl(&ds.fields, &trait_);
+ let padding_check = struct_padding_check(&ds.fields, &name);
+ quote::quote! {
+ #padding_check
+ // SAFETY: #name has no padding and all of its fields implement `AsBytes`
+ unsafe impl #trait_ for #name #where_clause {}
+ }
+}
+
+pub(crate) fn from_bytes(input: DeriveInput) -> TokenStream {
+ let syn::Data::Struct(ref ds) = &input.data else {
+ return quote::quote! { compile_error!("#[derive(FromBytes)] only supports structs") };
+ };
+ let (impl_generics, ty_generics, base_where_clause) = input.generics.split_for_impl();
+ let name = input.ident;
+ let trait_ = parse_quote! { ::kernel::transmute::FromBytes };
+ let mut where_clause = all_fields_impl(&ds.fields, &trait_);
+ if let Some(base_clause) = base_where_clause {
+ where_clause
+ .predicates
+ .extend(base_clause.predicates.clone())
+ };
+ quote::quote! {
+ // SAFETY: All fields of #name implement `FromBytes` and it is a struct, so there is no
+ // implicit discriminator.
+ unsafe impl #impl_generics #trait_ for #name #ty_generics #where_clause {}
+ }
+}
--
2.52.0.305.g3fc767764a-goog
Matthew,
> On 15 Dec 2025, at 21:44, Matthew Maurer <mmaurer@google.com> wrote:
>
> This provides a derive macro for `AsBytes` and `FromBytes` for structs
> only. For both, it checks the respective trait on every underlying
> field. For `AsBytes`, it emits a const-time padding check that will fail
> the compilation if derived on a type with padding.
>
> Signed-off-by: Matthew Maurer <mmaurer@google.com>
> ---
> rust/macros/lib.rs | 63 ++++++++++++++++++++++++++++++++++++++++++++++++
> rust/macros/transmute.rs | 58 ++++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 121 insertions(+)
>
> diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
> index b38002151871a33f6b4efea70be2deb6ddad38e2..d66397942529f67697f74a908e257cacc4201d84 100644
> --- a/rust/macros/lib.rs
> +++ b/rust/macros/lib.rs
> @@ -20,9 +20,14 @@
> mod kunit;
> mod module;
> mod paste;
> +mod transmute;
> mod vtable;
>
> use proc_macro::TokenStream;
> +use syn::{
> + parse_macro_input,
> + DeriveInput, //
> +};
>
> /// Declares a kernel module.
> ///
> @@ -475,3 +480,61 @@ pub fn paste(input: TokenStream) -> TokenStream {
> pub fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
> kunit::kunit_tests(attr, ts)
> }
> +
> +/// Implements `FromBytes` for a struct.
> +///
> +/// It will fail compilation if the struct you are deriving on cannot be determined to implement
> +/// `FromBytes` safely. It may still fail for some types which would be safe to implement
> +/// `FromBytes` for, in which case you will need to write the implementation and justification
> +/// yourself.
> +///
> +/// Main reasons your type may be rejected:
> +/// * Not a `struct`
> +/// * One of the fields is not `FromBytes`
> +///
> +/// # Examples
> +///
> +/// ```
> +/// #[derive(FromBytes)]
> +/// #[repr(C)]
> +/// struct Foo {
> +/// x: u32,
> +/// y: u16,
> +/// z: u16,
> +/// }
> +/// ```
> +#[proc_macro_derive(FromBytes)]
> +pub fn derive_from_bytes(tokens: TokenStream) -> TokenStream {
> + let input = parse_macro_input!(tokens as DeriveInput);
> + transmute::from_bytes(input).into()
> +}
> +
> +/// Implements `AsBytes` for a struct.
> +///
> +/// It will fail compilation if the struct you are deriving on cannot be determined to implement
> +/// `AsBytes` safely. It may still fail for some structures which would be safe to implement
> +/// `AsBytes`, in which case you will need to write the implementation and justification
> +/// yourself.
> +///
> +/// Main reasons your type may be rejected:
> +/// * Not a `struct`
> +/// * One of the fields is not `AsBytes`
> +/// * Your struct has generic parameters
> +/// * There is padding somewhere in your struct
Why is padding relevant here but not in FromBytes?
> +///
> +/// # Examples
> +///
> +/// ```
> +/// #[derive(AsBytes)]
> +/// #[repr(C)]
> +/// struct Foo {
> +/// x: u32,
> +/// y: u16,
> +/// z: u16,
> +/// }
> +/// ```
> +#[proc_macro_derive(AsBytes)]
> +pub fn derive_as_bytes(tokens: TokenStream) -> TokenStream {
> + let input = parse_macro_input!(tokens as DeriveInput);
> + transmute::as_bytes(input).into()
> +}
> diff --git a/rust/macros/transmute.rs b/rust/macros/transmute.rs
> new file mode 100644
> index 0000000000000000000000000000000000000000..43cf36a1334f1fed23c0e777026392f987f78d8d
> --- /dev/null
> +++ b/rust/macros/transmute.rs
> @@ -0,0 +1,58 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +use proc_macro2::TokenStream;
> +use syn::{parse_quote, DeriveInput, Fields, Ident, ItemConst, Path, WhereClause};
> +
> +fn all_fields_impl(fields: &Fields, trait_: &Path) -> WhereClause {
> + let tys = fields.iter().map(|field| &field.ty);
> + parse_quote! {
> + where #(for<'a> #tys: #trait_),*
Why do we need this hrtb here?
> + }
> +}
> +
> +fn struct_padding_check(fields: &Fields, name: &Ident) -> ItemConst {
> + let tys = fields.iter().map(|field| &field.ty);
> + parse_quote! {
> + const _: () = {
> + assert!(#(core::mem::size_of::<#tys>())+* == core::mem::size_of::<#name>());
> + };
> + }
> +}
> +
> +pub(crate) fn as_bytes(input: DeriveInput) -> TokenStream {
> + if !input.generics.params.is_empty() {
> + return quote::quote! { compile_error!("#[derive(AsBytes)] does not support generics") };
> + }
> + let syn::Data::Struct(ref ds) = &input.data else {
> + return quote::quote! { compile_error!("#[derive(AsBytes)] only supports structs") };
> + };
> + let name = input.ident;
> + let trait_ = parse_quote! { ::kernel::transmute::AsBytes };
> + let where_clause = all_fields_impl(&ds.fields, &trait_);
> + let padding_check = struct_padding_check(&ds.fields, &name);
> + quote::quote! {
> + #padding_check
> + // SAFETY: #name has no padding and all of its fields implement `AsBytes`
> + unsafe impl #trait_ for #name #where_clause {}
> + }
In general I’d add blanks.
> +}
> +
> +pub(crate) fn from_bytes(input: DeriveInput) -> TokenStream {
> + let syn::Data::Struct(ref ds) = &input.data else {
> + return quote::quote! { compile_error!("#[derive(FromBytes)] only supports structs") };
> + };
> + let (impl_generics, ty_generics, base_where_clause) = input.generics.split_for_impl();
> + let name = input.ident;
> + let trait_ = parse_quote! { ::kernel::transmute::FromBytes };
> + let mut where_clause = all_fields_impl(&ds.fields, &trait_);
> + if let Some(base_clause) = base_where_clause {
> + where_clause
> + .predicates
> + .extend(base_clause.predicates.clone())
> + };
> + quote::quote! {
> + // SAFETY: All fields of #name implement `FromBytes` and it is a struct, so there is no
> + // implicit discriminator.
> + unsafe impl #impl_generics #trait_ for #name #ty_generics #where_clause {}
> + }
> +}
>
> --
> 2.52.0.305.g3fc767764a-goog
>
>
Overall looks good. Please chime in on the two questions above.
— Daniel
On Wed, Dec 17, 2025 at 9:36 AM Daniel Almeida
<daniel.almeida@collabora.com> wrote:
>
> Matthew,
>
> > On 15 Dec 2025, at 21:44, Matthew Maurer <mmaurer@google.com> wrote:
> >
> > This provides a derive macro for `AsBytes` and `FromBytes` for structs
> > only. For both, it checks the respective trait on every underlying
> > field. For `AsBytes`, it emits a const-time padding check that will fail
> > the compilation if derived on a type with padding.
> >
> > Signed-off-by: Matthew Maurer <mmaurer@google.com>
> > ---
> > rust/macros/lib.rs | 63 ++++++++++++++++++++++++++++++++++++++++++++++++
> > rust/macros/transmute.rs | 58 ++++++++++++++++++++++++++++++++++++++++++++
> > 2 files changed, 121 insertions(+)
> >
> > diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
> > index b38002151871a33f6b4efea70be2deb6ddad38e2..d66397942529f67697f74a908e257cacc4201d84 100644
> > --- a/rust/macros/lib.rs
> > +++ b/rust/macros/lib.rs
> > @@ -20,9 +20,14 @@
> > mod kunit;
> > mod module;
> > mod paste;
> > +mod transmute;
> > mod vtable;
> >
> > use proc_macro::TokenStream;
> > +use syn::{
> > + parse_macro_input,
> > + DeriveInput, //
> > +};
> >
> > /// Declares a kernel module.
> > ///
> > @@ -475,3 +480,61 @@ pub fn paste(input: TokenStream) -> TokenStream {
> > pub fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
> > kunit::kunit_tests(attr, ts)
> > }
> > +
> > +/// Implements `FromBytes` for a struct.
> > +///
> > +/// It will fail compilation if the struct you are deriving on cannot be determined to implement
> > +/// `FromBytes` safely. It may still fail for some types which would be safe to implement
> > +/// `FromBytes` for, in which case you will need to write the implementation and justification
> > +/// yourself.
> > +///
> > +/// Main reasons your type may be rejected:
> > +/// * Not a `struct`
> > +/// * One of the fields is not `FromBytes`
> > +///
> > +/// # Examples
> > +///
> > +/// ```
> > +/// #[derive(FromBytes)]
> > +/// #[repr(C)]
> > +/// struct Foo {
> > +/// x: u32,
> > +/// y: u16,
> > +/// z: u16,
> > +/// }
> > +/// ```
> > +#[proc_macro_derive(FromBytes)]
> > +pub fn derive_from_bytes(tokens: TokenStream) -> TokenStream {
> > + let input = parse_macro_input!(tokens as DeriveInput);
> > + transmute::from_bytes(input).into()
> > +}
> > +
> > +/// Implements `AsBytes` for a struct.
> > +///
> > +/// It will fail compilation if the struct you are deriving on cannot be determined to implement
> > +/// `AsBytes` safely. It may still fail for some structures which would be safe to implement
> > +/// `AsBytes`, in which case you will need to write the implementation and justification
> > +/// yourself.
> > +///
> > +/// Main reasons your type may be rejected:
> > +/// * Not a `struct`
> > +/// * One of the fields is not `AsBytes`
> > +/// * Your struct has generic parameters
> > +/// * There is padding somewhere in your struct
>
> Why is padding relevant here but not in FromBytes?
Padding bytes can be initialized to any value, but it is UB to observe
their contents because they may be implicitly uninitialized[1]. This
also matches the approach of `zerocopy`[2], which is the standard for
these sorts of transmutations outside the kernel - any byte can go
into the padding, but you cannot *read* any byte out of the padding.
[1]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html#r-undefined.validity.undef
[2]: https://docs.rs/zerocopy/latest/zerocopy/trait.FromBytes.html#warning-padding-bytes
>
> > +///
> > +/// # Examples
> > +///
> > +/// ```
> > +/// #[derive(AsBytes)]
> > +/// #[repr(C)]
> > +/// struct Foo {
> > +/// x: u32,
> > +/// y: u16,
> > +/// z: u16,
> > +/// }
> > +/// ```
> > +#[proc_macro_derive(AsBytes)]
> > +pub fn derive_as_bytes(tokens: TokenStream) -> TokenStream {
> > + let input = parse_macro_input!(tokens as DeriveInput);
> > + transmute::as_bytes(input).into()
> > +}
> > diff --git a/rust/macros/transmute.rs b/rust/macros/transmute.rs
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..43cf36a1334f1fed23c0e777026392f987f78d8d
> > --- /dev/null
> > +++ b/rust/macros/transmute.rs
> > @@ -0,0 +1,58 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +
> > +use proc_macro2::TokenStream;
> > +use syn::{parse_quote, DeriveInput, Fields, Ident, ItemConst, Path, WhereClause};
> > +
> > +fn all_fields_impl(fields: &Fields, trait_: &Path) -> WhereClause {
> > + let tys = fields.iter().map(|field| &field.ty);
> > + parse_quote! {
> > + where #(for<'a> #tys: #trait_),*
>
> Why do we need this hrtb here?
It's a workaround to avoid needing `#![feature(trivial_bounds)]`.
Without it, generated code for, say, `where *const T: FromBytes` will
immediately cause a compilation error. With it, it will simply cause
the trait's requirements to be unfulfilled.
>
> > + }
> > +}
> > +
> > +fn struct_padding_check(fields: &Fields, name: &Ident) -> ItemConst {
> > + let tys = fields.iter().map(|field| &field.ty);
> > + parse_quote! {
> > + const _: () = {
> > + assert!(#(core::mem::size_of::<#tys>())+* == core::mem::size_of::<#name>());
> > + };
> > + }
> > +}
> > +
> > +pub(crate) fn as_bytes(input: DeriveInput) -> TokenStream {
> > + if !input.generics.params.is_empty() {
> > + return quote::quote! { compile_error!("#[derive(AsBytes)] does not support generics") };
> > + }
> > + let syn::Data::Struct(ref ds) = &input.data else {
> > + return quote::quote! { compile_error!("#[derive(AsBytes)] only supports structs") };
> > + };
> > + let name = input.ident;
> > + let trait_ = parse_quote! { ::kernel::transmute::AsBytes };
> > + let where_clause = all_fields_impl(&ds.fields, &trait_);
> > + let padding_check = struct_padding_check(&ds.fields, &name);
> > + quote::quote! {
> > + #padding_check
> > + // SAFETY: #name has no padding and all of its fields implement `AsBytes`
> > + unsafe impl #trait_ for #name #where_clause {}
> > + }
>
> In general I’d add blanks.
I'm not sure what you're suggesting here.
>
> > +}
> > +
> > +pub(crate) fn from_bytes(input: DeriveInput) -> TokenStream {
> > + let syn::Data::Struct(ref ds) = &input.data else {
> > + return quote::quote! { compile_error!("#[derive(FromBytes)] only supports structs") };
> > + };
> > + let (impl_generics, ty_generics, base_where_clause) = input.generics.split_for_impl();
> > + let name = input.ident;
> > + let trait_ = parse_quote! { ::kernel::transmute::FromBytes };
> > + let mut where_clause = all_fields_impl(&ds.fields, &trait_);
> > + if let Some(base_clause) = base_where_clause {
> > + where_clause
> > + .predicates
> > + .extend(base_clause.predicates.clone())
> > + };
> > + quote::quote! {
> > + // SAFETY: All fields of #name implement `FromBytes` and it is a struct, so there is no
> > + // implicit discriminator.
> > + unsafe impl #impl_generics #trait_ for #name #ty_generics #where_clause {}
> > + }
> > +}
> >
> > --
> > 2.52.0.305.g3fc767764a-goog
> >
> >
>
> Overall looks good. Please chime in on the two questions above.
>
> — Daniel
>
> On 17 Dec 2025, at 14:57, Matthew Maurer <mmaurer@google.com> wrote:
>
> On Wed, Dec 17, 2025 at 9:36 AM Daniel Almeida
> <daniel.almeida@collabora.com> wrote:
>>
>> Matthew,
>>
>>> On 15 Dec 2025, at 21:44, Matthew Maurer <mmaurer@google.com> wrote:
>>>
>>> This provides a derive macro for `AsBytes` and `FromBytes` for structs
>>> only. For both, it checks the respective trait on every underlying
>>> field. For `AsBytes`, it emits a const-time padding check that will fail
>>> the compilation if derived on a type with padding.
>>>
>>> Signed-off-by: Matthew Maurer <mmaurer@google.com>
>>> ---
>>> rust/macros/lib.rs | 63 ++++++++++++++++++++++++++++++++++++++++++++++++
>>> rust/macros/transmute.rs | 58 ++++++++++++++++++++++++++++++++++++++++++++
>>> 2 files changed, 121 insertions(+)
>>>
>>> diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
>>> index b38002151871a33f6b4efea70be2deb6ddad38e2..d66397942529f67697f74a908e257cacc4201d84 100644
>>> --- a/rust/macros/lib.rs
>>> +++ b/rust/macros/lib.rs
>>> @@ -20,9 +20,14 @@
>>> mod kunit;
>>> mod module;
>>> mod paste;
>>> +mod transmute;
>>> mod vtable;
>>>
>>> use proc_macro::TokenStream;
>>> +use syn::{
>>> + parse_macro_input,
>>> + DeriveInput, //
>>> +};
>>>
>>> /// Declares a kernel module.
>>> ///
>>> @@ -475,3 +480,61 @@ pub fn paste(input: TokenStream) -> TokenStream {
>>> pub fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
>>> kunit::kunit_tests(attr, ts)
>>> }
>>> +
>>> +/// Implements `FromBytes` for a struct.
>>> +///
>>> +/// It will fail compilation if the struct you are deriving on cannot be determined to implement
>>> +/// `FromBytes` safely. It may still fail for some types which would be safe to implement
>>> +/// `FromBytes` for, in which case you will need to write the implementation and justification
>>> +/// yourself.
>>> +///
>>> +/// Main reasons your type may be rejected:
>>> +/// * Not a `struct`
>>> +/// * One of the fields is not `FromBytes`
>>> +///
>>> +/// # Examples
>>> +///
>>> +/// ```
>>> +/// #[derive(FromBytes)]
>>> +/// #[repr(C)]
>>> +/// struct Foo {
>>> +/// x: u32,
>>> +/// y: u16,
>>> +/// z: u16,
>>> +/// }
>>> +/// ```
>>> +#[proc_macro_derive(FromBytes)]
>>> +pub fn derive_from_bytes(tokens: TokenStream) -> TokenStream {
>>> + let input = parse_macro_input!(tokens as DeriveInput);
>>> + transmute::from_bytes(input).into()
>>> +}
>>> +
>>> +/// Implements `AsBytes` for a struct.
>>> +///
>>> +/// It will fail compilation if the struct you are deriving on cannot be determined to implement
>>> +/// `AsBytes` safely. It may still fail for some structures which would be safe to implement
>>> +/// `AsBytes`, in which case you will need to write the implementation and justification
>>> +/// yourself.
>>> +///
>>> +/// Main reasons your type may be rejected:
>>> +/// * Not a `struct`
>>> +/// * One of the fields is not `AsBytes`
>>> +/// * Your struct has generic parameters
>>> +/// * There is padding somewhere in your struct
>>
>> Why is padding relevant here but not in FromBytes?
>
> Padding bytes can be initialized to any value, but it is UB to observe
> their contents because they may be implicitly uninitialized[1]. This
> also matches the approach of `zerocopy`[2], which is the standard for
> these sorts of transmutations outside the kernel - any byte can go
> into the padding, but you cannot *read* any byte out of the padding.
>
> [1]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html#r-undefined.validity.undef
> [2]: https://docs.rs/zerocopy/latest/zerocopy/trait.FromBytes.html#warning-padding-bytes
Ack
>
>>
>>> +///
>>> +/// # Examples
>>> +///
>>> +/// ```
>>> +/// #[derive(AsBytes)]
>>> +/// #[repr(C)]
>>> +/// struct Foo {
>>> +/// x: u32,
>>> +/// y: u16,
>>> +/// z: u16,
>>> +/// }
>>> +/// ```
>>> +#[proc_macro_derive(AsBytes)]
>>> +pub fn derive_as_bytes(tokens: TokenStream) -> TokenStream {
>>> + let input = parse_macro_input!(tokens as DeriveInput);
>>> + transmute::as_bytes(input).into()
>>> +}
>>> diff --git a/rust/macros/transmute.rs b/rust/macros/transmute.rs
>>> new file mode 100644
>>> index 0000000000000000000000000000000000000000..43cf36a1334f1fed23c0e777026392f987f78d8d
>>> --- /dev/null
>>> +++ b/rust/macros/transmute.rs
>>> @@ -0,0 +1,58 @@
>>> +// SPDX-License-Identifier: GPL-2.0
>>> +
>>> +use proc_macro2::TokenStream;
>>> +use syn::{parse_quote, DeriveInput, Fields, Ident, ItemConst, Path, WhereClause};
>>> +
>>> +fn all_fields_impl(fields: &Fields, trait_: &Path) -> WhereClause {
>>> + let tys = fields.iter().map(|field| &field.ty);
>>> + parse_quote! {
>>> + where #(for<'a> #tys: #trait_),*
>>
>> Why do we need this hrtb here?
>
> It's a workaround to avoid needing `#![feature(trivial_bounds)]`.
> Without it, generated code for, say, `where *const T: FromBytes` will
> immediately cause a compilation error. With it, it will simply cause
> the trait's requirements to be unfulfilled.
Perhaps add this as a comment. It’s not clear at all that this is a workaround at first.
>
>>
>>> + }
>>> +}
>>> +
>>> +fn struct_padding_check(fields: &Fields, name: &Ident) -> ItemConst {
>>> + let tys = fields.iter().map(|field| &field.ty);
>>> + parse_quote! {
>>> + const _: () = {
>>> + assert!(#(core::mem::size_of::<#tys>())+* == core::mem::size_of::<#name>());
>>> + };
>>> + }
>>> +}
>>> +
>>> +pub(crate) fn as_bytes(input: DeriveInput) -> TokenStream {
>>> + if !input.generics.params.is_empty() {
>>> + return quote::quote! { compile_error!("#[derive(AsBytes)] does not support generics") };
>>> + }
>>> + let syn::Data::Struct(ref ds) = &input.data else {
>>> + return quote::quote! { compile_error!("#[derive(AsBytes)] only supports structs") };
>>> + };
>>> + let name = input.ident;
>>> + let trait_ = parse_quote! { ::kernel::transmute::AsBytes };
>>> + let where_clause = all_fields_impl(&ds.fields, &trait_);
>>> + let padding_check = struct_padding_check(&ds.fields, &name);
>>> + quote::quote! {
>>> + #padding_check
>>> + // SAFETY: #name has no padding and all of its fields implement `AsBytes`
>>> + unsafe impl #trait_ for #name #where_clause {}
>>> + }
>>
>> In general I’d add blanks.
>
> I'm not sure what you're suggesting here.
Add blank lines to break this thing up for readability. This entire function
has 0 blank lines, it’s a bit hard to read. Anyways, this is just a nit.
>
>>
>>> +}
>>> +
>>> +pub(crate) fn from_bytes(input: DeriveInput) -> TokenStream {
>>> + let syn::Data::Struct(ref ds) = &input.data else {
>>> + return quote::quote! { compile_error!("#[derive(FromBytes)] only supports structs") };
>>> + };
>>> + let (impl_generics, ty_generics, base_where_clause) = input.generics.split_for_impl();
>>> + let name = input.ident;
>>> + let trait_ = parse_quote! { ::kernel::transmute::FromBytes };
>>> + let mut where_clause = all_fields_impl(&ds.fields, &trait_);
>>> + if let Some(base_clause) = base_where_clause {
>>> + where_clause
>>> + .predicates
>>> + .extend(base_clause.predicates.clone())
>>> + };
>>> + quote::quote! {
>>> + // SAFETY: All fields of #name implement `FromBytes` and it is a struct, so there is no
>>> + // implicit discriminator.
>>> + unsafe impl #impl_generics #trait_ for #name #ty_generics #where_clause {}
>>> + }
>>> +}
>>>
>>> --
>>> 2.52.0.305.g3fc767764a-goog
>>>
>>>
>>
>> Overall looks good. Please chime in on the two questions above.
>>
>> — Daniel
On Tue Dec 16, 2025 at 9:44 AM JST, Matthew Maurer wrote:
> This provides a derive macro for `AsBytes` and `FromBytes` for structs
> only. For both, it checks the respective trait on every underlying
> field. For `AsBytes`, it emits a const-time padding check that will fail
> the compilation if derived on a type with padding.
>
> Signed-off-by: Matthew Maurer <mmaurer@google.com>
I like this a lot. We have a bunch of unsafe impls in Nova that this
could help us get rid of.
Amazed that this even seems to work on tuple structs!
> ---
> rust/macros/lib.rs | 63 ++++++++++++++++++++++++++++++++++++++++++++++++
> rust/macros/transmute.rs | 58 ++++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 121 insertions(+)
>
> diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
> index b38002151871a33f6b4efea70be2deb6ddad38e2..d66397942529f67697f74a908e257cacc4201d84 100644
> --- a/rust/macros/lib.rs
> +++ b/rust/macros/lib.rs
> @@ -20,9 +20,14 @@
> mod kunit;
> mod module;
> mod paste;
> +mod transmute;
> mod vtable;
>
> use proc_macro::TokenStream;
> +use syn::{
> + parse_macro_input,
> + DeriveInput, //
> +};
>
> /// Declares a kernel module.
> ///
> @@ -475,3 +480,61 @@ pub fn paste(input: TokenStream) -> TokenStream {
> pub fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
> kunit::kunit_tests(attr, ts)
> }
> +
> +/// Implements `FromBytes` for a struct.
> +///
> +/// It will fail compilation if the struct you are deriving on cannot be determined to implement
> +/// `FromBytes` safely. It may still fail for some types which would be safe to implement
> +/// `FromBytes` for, in which case you will need to write the implementation and justification
> +/// yourself.
> +///
> +/// Main reasons your type may be rejected:
> +/// * Not a `struct`
> +/// * One of the fields is not `FromBytes`
> +///
> +/// # Examples
> +///
> +/// ```
> +/// #[derive(FromBytes)]
> +/// #[repr(C)]
> +/// struct Foo {
> +/// x: u32,
> +/// y: u16,
> +/// z: u16,
> +/// }
> +/// ```
One thing I have noticed is that I could sucessfully derive `FromBytes`
on a struct that is not `repr(C)`... Is that something we want to
disallow?
On Tue, Dec 16, 2025 at 7:12 PM Alexandre Courbot <acourbot@nvidia.com> wrote:
>
> On Tue Dec 16, 2025 at 9:44 AM JST, Matthew Maurer wrote:
> > This provides a derive macro for `AsBytes` and `FromBytes` for structs
> > only. For both, it checks the respective trait on every underlying
> > field. For `AsBytes`, it emits a const-time padding check that will fail
> > the compilation if derived on a type with padding.
> >
> > Signed-off-by: Matthew Maurer <mmaurer@google.com>
>
> I like this a lot. We have a bunch of unsafe impls in Nova that this
> could help us get rid of.
>
> Amazed that this even seems to work on tuple structs!
>
> > ---
> > rust/macros/lib.rs | 63 ++++++++++++++++++++++++++++++++++++++++++++++++
> > rust/macros/transmute.rs | 58 ++++++++++++++++++++++++++++++++++++++++++++
> > 2 files changed, 121 insertions(+)
> >
> > diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
> > index b38002151871a33f6b4efea70be2deb6ddad38e2..d66397942529f67697f74a908e257cacc4201d84 100644
> > --- a/rust/macros/lib.rs
> > +++ b/rust/macros/lib.rs
> > @@ -20,9 +20,14 @@
> > mod kunit;
> > mod module;
> > mod paste;
> > +mod transmute;
> > mod vtable;
> >
> > use proc_macro::TokenStream;
> > +use syn::{
> > + parse_macro_input,
> > + DeriveInput, //
> > +};
> >
> > /// Declares a kernel module.
> > ///
> > @@ -475,3 +480,61 @@ pub fn paste(input: TokenStream) -> TokenStream {
> > pub fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
> > kunit::kunit_tests(attr, ts)
> > }
> > +
> > +/// Implements `FromBytes` for a struct.
> > +///
> > +/// It will fail compilation if the struct you are deriving on cannot be determined to implement
> > +/// `FromBytes` safely. It may still fail for some types which would be safe to implement
> > +/// `FromBytes` for, in which case you will need to write the implementation and justification
> > +/// yourself.
> > +///
> > +/// Main reasons your type may be rejected:
> > +/// * Not a `struct`
> > +/// * One of the fields is not `FromBytes`
> > +///
> > +/// # Examples
> > +///
> > +/// ```
> > +/// #[derive(FromBytes)]
> > +/// #[repr(C)]
> > +/// struct Foo {
> > +/// x: u32,
> > +/// y: u16,
> > +/// z: u16,
> > +/// }
> > +/// ```
>
> One thing I have noticed is that I could sucessfully derive `FromBytes`
> on a struct that is not `repr(C)`... Is that something we want to
> disallow?
>
Why should we disallow this? I can enforce it very easily if we want
it, but the only difference between `#[repr(C)]` and `#[repr(Rust)]`
is whether we can statically predict their layout. In theory you can
use this to elide the padding check for `#[repr(C)]` structs (and
`zerocopy` does this), but it's significantly more complicated.
The only argument I see in favor of disallowing `#[repr(Rust)]` here
is that if it's not a struct that also supports `AsBytes`, there's a
question about where you're getting the bytes to load from.
I will point out that we probably don't *just* want to restrict to
`#[repr(C)]` because `#[repr(transparent)]` and `#[repr(packed)]` are
also great use cases.
On Thu Dec 18, 2025 at 3:01 AM JST, Matthew Maurer wrote:
> On Tue, Dec 16, 2025 at 7:12 PM Alexandre Courbot <acourbot@nvidia.com> wrote:
>>
>> On Tue Dec 16, 2025 at 9:44 AM JST, Matthew Maurer wrote:
>> > This provides a derive macro for `AsBytes` and `FromBytes` for structs
>> > only. For both, it checks the respective trait on every underlying
>> > field. For `AsBytes`, it emits a const-time padding check that will fail
>> > the compilation if derived on a type with padding.
>> >
>> > Signed-off-by: Matthew Maurer <mmaurer@google.com>
>>
>> I like this a lot. We have a bunch of unsafe impls in Nova that this
>> could help us get rid of.
>>
>> Amazed that this even seems to work on tuple structs!
>>
>> > ---
>> > rust/macros/lib.rs | 63 ++++++++++++++++++++++++++++++++++++++++++++++++
>> > rust/macros/transmute.rs | 58 ++++++++++++++++++++++++++++++++++++++++++++
>> > 2 files changed, 121 insertions(+)
>> >
>> > diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
>> > index b38002151871a33f6b4efea70be2deb6ddad38e2..d66397942529f67697f74a908e257cacc4201d84 100644
>> > --- a/rust/macros/lib.rs
>> > +++ b/rust/macros/lib.rs
>> > @@ -20,9 +20,14 @@
>> > mod kunit;
>> > mod module;
>> > mod paste;
>> > +mod transmute;
>> > mod vtable;
>> >
>> > use proc_macro::TokenStream;
>> > +use syn::{
>> > + parse_macro_input,
>> > + DeriveInput, //
>> > +};
>> >
>> > /// Declares a kernel module.
>> > ///
>> > @@ -475,3 +480,61 @@ pub fn paste(input: TokenStream) -> TokenStream {
>> > pub fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
>> > kunit::kunit_tests(attr, ts)
>> > }
>> > +
>> > +/// Implements `FromBytes` for a struct.
>> > +///
>> > +/// It will fail compilation if the struct you are deriving on cannot be determined to implement
>> > +/// `FromBytes` safely. It may still fail for some types which would be safe to implement
>> > +/// `FromBytes` for, in which case you will need to write the implementation and justification
>> > +/// yourself.
>> > +///
>> > +/// Main reasons your type may be rejected:
>> > +/// * Not a `struct`
>> > +/// * One of the fields is not `FromBytes`
>> > +///
>> > +/// # Examples
>> > +///
>> > +/// ```
>> > +/// #[derive(FromBytes)]
>> > +/// #[repr(C)]
>> > +/// struct Foo {
>> > +/// x: u32,
>> > +/// y: u16,
>> > +/// z: u16,
>> > +/// }
>> > +/// ```
>>
>> One thing I have noticed is that I could sucessfully derive `FromBytes`
>> on a struct that is not `repr(C)`... Is that something we want to
>> disallow?
>>
>
> Why should we disallow this? I can enforce it very easily if we want
> it, but the only difference between `#[repr(C)]` and `#[repr(Rust)]`
> is whether we can statically predict their layout. In theory you can
> use this to elide the padding check for `#[repr(C)]` structs (and
> `zerocopy` does this), but it's significantly more complicated.
>
> The only argument I see in favor of disallowing `#[repr(Rust)]` here
> is that if it's not a struct that also supports `AsBytes`, there's a
> question about where you're getting the bytes to load from.
>
> I will point out that we probably don't *just* want to restrict to
> `#[repr(C)]` because `#[repr(transparent)]` and `#[repr(packed)]` are
> also great use cases.
Yeah it's probably correct as it is. I am not sure why we would want to
use it on types without a predictable layout, but also cannot say this
is fundamentally broken.
On Thu, Dec 18, 2025 at 04:23:43PM +0900, Alexandre Courbot wrote:
> On Thu Dec 18, 2025 at 3:01 AM JST, Matthew Maurer wrote:
> > On Tue, Dec 16, 2025 at 7:12 PM Alexandre Courbot <acourbot@nvidia.com> wrote:
> >> > +/// Implements `FromBytes` for a struct.
> >> > +///
> >> > +/// It will fail compilation if the struct you are deriving on cannot be determined to implement
> >> > +/// `FromBytes` safely. It may still fail for some types which would be safe to implement
> >> > +/// `FromBytes` for, in which case you will need to write the implementation and justification
> >> > +/// yourself.
> >> > +///
> >> > +/// Main reasons your type may be rejected:
> >> > +/// * Not a `struct`
> >> > +/// * One of the fields is not `FromBytes`
> >> > +///
> >> > +/// # Examples
> >> > +///
> >> > +/// ```
> >> > +/// #[derive(FromBytes)]
> >> > +/// #[repr(C)]
> >> > +/// struct Foo {
> >> > +/// x: u32,
> >> > +/// y: u16,
> >> > +/// z: u16,
> >> > +/// }
> >> > +/// ```
> >>
> >> One thing I have noticed is that I could sucessfully derive `FromBytes`
> >> on a struct that is not `repr(C)`... Is that something we want to
> >> disallow?
> >>
> >
> > Why should we disallow this? I can enforce it very easily if we want
> > it, but the only difference between `#[repr(C)]` and `#[repr(Rust)]`
> > is whether we can statically predict their layout. In theory you can
> > use this to elide the padding check for `#[repr(C)]` structs (and
> > `zerocopy` does this), but it's significantly more complicated.
> >
> > The only argument I see in favor of disallowing `#[repr(Rust)]` here
> > is that if it's not a struct that also supports `AsBytes`, there's a
> > question about where you're getting the bytes to load from.
> >
> > I will point out that we probably don't *just* want to restrict to
> > `#[repr(C)]` because `#[repr(transparent)]` and `#[repr(packed)]` are
> > also great use cases.
>
> Yeah it's probably correct as it is. I am not sure why we would want to
> use it on types without a predictable layout, but also cannot say this
> is fundamentally broken.
At the very least such types can roundtrip to/from byte arrays. Or you
could pass them to the randomness pool:
https://lore.kernel.org/all/20251216-add-entropy-v2-1-4d866f251474@google.com/
Alice
> On 17 Dec 2025, at 15:01, Matthew Maurer <mmaurer@google.com> wrote:
>
> On Tue, Dec 16, 2025 at 7:12 PM Alexandre Courbot <acourbot@nvidia.com> wrote:
>>
>> On Tue Dec 16, 2025 at 9:44 AM JST, Matthew Maurer wrote:
>>> This provides a derive macro for `AsBytes` and `FromBytes` for structs
>>> only. For both, it checks the respective trait on every underlying
>>> field. For `AsBytes`, it emits a const-time padding check that will fail
>>> the compilation if derived on a type with padding.
>>>
>>> Signed-off-by: Matthew Maurer <mmaurer@google.com>
>>
>> I like this a lot. We have a bunch of unsafe impls in Nova that this
>> could help us get rid of.
>>
>> Amazed that this even seems to work on tuple structs!
>>
>>> ---
>>> rust/macros/lib.rs | 63 ++++++++++++++++++++++++++++++++++++++++++++++++
>>> rust/macros/transmute.rs | 58 ++++++++++++++++++++++++++++++++++++++++++++
>>> 2 files changed, 121 insertions(+)
>>>
>>> diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
>>> index b38002151871a33f6b4efea70be2deb6ddad38e2..d66397942529f67697f74a908e257cacc4201d84 100644
>>> --- a/rust/macros/lib.rs
>>> +++ b/rust/macros/lib.rs
>>> @@ -20,9 +20,14 @@
>>> mod kunit;
>>> mod module;
>>> mod paste;
>>> +mod transmute;
>>> mod vtable;
>>>
>>> use proc_macro::TokenStream;
>>> +use syn::{
>>> + parse_macro_input,
>>> + DeriveInput, //
>>> +};
>>>
>>> /// Declares a kernel module.
>>> ///
>>> @@ -475,3 +480,61 @@ pub fn paste(input: TokenStream) -> TokenStream {
>>> pub fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
>>> kunit::kunit_tests(attr, ts)
>>> }
>>> +
>>> +/// Implements `FromBytes` for a struct.
>>> +///
>>> +/// It will fail compilation if the struct you are deriving on cannot be determined to implement
>>> +/// `FromBytes` safely. It may still fail for some types which would be safe to implement
>>> +/// `FromBytes` for, in which case you will need to write the implementation and justification
>>> +/// yourself.
>>> +///
>>> +/// Main reasons your type may be rejected:
>>> +/// * Not a `struct`
>>> +/// * One of the fields is not `FromBytes`
>>> +///
>>> +/// # Examples
>>> +///
>>> +/// ```
>>> +/// #[derive(FromBytes)]
>>> +/// #[repr(C)]
>>> +/// struct Foo {
>>> +/// x: u32,
>>> +/// y: u16,
>>> +/// z: u16,
>>> +/// }
>>> +/// ```
>>
>> One thing I have noticed is that I could sucessfully derive `FromBytes`
>> on a struct that is not `repr(C)`... Is that something we want to
>> disallow?
>>
>
> Why should we disallow this? I can enforce it very easily if we want
> it, but the only difference between `#[repr(C)]` and `#[repr(Rust)]`
> is whether we can statically predict their layout. In theory you can
> use this to elide the padding check for `#[repr(C)]` structs (and
> `zerocopy` does this), but it's significantly more complicated.
>
> The only argument I see in favor of disallowing `#[repr(Rust)]` here
> is that if it's not a struct that also supports `AsBytes`, there's a
> question about where you're getting the bytes to load from.
>
> I will point out that we probably don't *just* want to restrict to
> `#[repr(C)]` because `#[repr(transparent)]` and `#[repr(packed)]` are
> also great use cases.
>
I don’t see the point of disallowing other reprs. You can currently derive
FromBytes/AsBytes for any repr anyways. It’s up to the caller to make sure
that deriving these traits make sense.
© 2016 - 2025 Red Hat, Inc.