From d1c9d119b3f2b04c937c8e5d2efffbd6be14480c Mon Sep 17 00:00:00 2001 From: jiangjianfeng Date: Fri, 16 Jan 2026 08:22:15 +0000 Subject: [PATCH] Add macro for padding struct --- Cargo.lock | 10 + Cargo.toml | 1 + Makefile | 1 + ostd/libs/padding-struct/Cargo.toml | 24 ++ ostd/libs/padding-struct/README.md | 77 +++++ ostd/libs/padding-struct/src/lib.rs | 214 ++++++++++++++ .../padding-struct/tests/integration_test.rs | 267 ++++++++++++++++++ 7 files changed, 594 insertions(+) create mode 100644 ostd/libs/padding-struct/Cargo.toml create mode 100644 ostd/libs/padding-struct/README.md create mode 100644 ostd/libs/padding-struct/src/lib.rs create mode 100644 ostd/libs/padding-struct/tests/integration_test.rs diff --git a/Cargo.lock b/Cargo.lock index 6c3e134c0..2a96163be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1401,6 +1401,16 @@ version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" +[[package]] +name = "padding-struct" +version = "0.1.0" +dependencies = [ + "bytemuck", + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "paste" version = "1.0.15" diff --git a/Cargo.toml b/Cargo.toml index 13a7c97bc..81d293aa9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ members = [ "ostd/libs/linux-bzimage/setup", "ostd/libs/ostd-macros", "ostd/libs/ostd-test", + "ostd/libs/padding-struct", "kernel", "kernel/comps/block", "kernel/comps/cmdline", diff --git a/Makefile b/Makefile index 7bac4db33..08b61bda9 100644 --- a/Makefile +++ b/Makefile @@ -213,6 +213,7 @@ NON_OSDK_CRATES := \ ostd/libs/linux-bzimage/builder \ ostd/libs/ostd-macros \ ostd/libs/ostd-test \ + ostd/libs/padding-struct \ kernel/libs/aster-rights \ kernel/libs/aster-rights-proc \ kernel/libs/atomic-integer-wrapper \ diff --git a/ostd/libs/padding-struct/Cargo.toml b/ostd/libs/padding-struct/Cargo.toml new file mode 100644 index 000000000..4fe6d7805 --- /dev/null +++ b/ostd/libs/padding-struct/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "padding-struct" +version = "0.2.0" +description = "A procedural macro for automatically adding explicit padding fields to #[repr(C)] structs" +readme = "README.md" +repository.workspace = true +license.workspace = true +edition.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +proc-macro = true + +[dependencies] +proc-macro2.workspace = true +quote.workspace = true +syn.workspace = true + +[dev-dependencies] +zerocopy.workspace = true + +[lints] +workspace = true \ No newline at end of file diff --git a/ostd/libs/padding-struct/README.md b/ostd/libs/padding-struct/README.md new file mode 100644 index 000000000..d476aefc2 --- /dev/null +++ b/ostd/libs/padding-struct/README.md @@ -0,0 +1,77 @@ + + +# padding-struct + +A Rust procedural macro for automatically adding explicit padding fields to `#[repr(C)]` structs. + +## Overview + +When working with `#[repr(C)]` structs, +the Rust compiler automatically adds padding bytes to ensure proper alignment. +The `#[padding_struct]` macro makes these padding bytes explicit +by automatically generating padding fields in your struct definitions. + +### Basic Example + +```rust +use padding_struct::padding_struct; + +#[repr(C)] +#[padding_struct] +struct MyStruct { + a: u8, // 1 byte + b: u32, // 4 bytes (aligned to 4-byte boundary) + c: u16, // 2 bytes +} +``` + +The macro generates a new struct with explicit padding bytes: + +```rust +// Padded struct (the one you'll use) +#[repr(C)] +struct MyStruct { + a: u8, + pub __pad1: [u8; 3], // padding before `b` + b: u32, + pub __pad2: [u8; 0], // no padding before `c` + c: u16, + pub __pad3: [u8; 2], // trailing padding +} +``` + +### Integration with zerocopy + +The generated structs work seamlessly with `zerocopy` for safe transmutation: + +```rust +use padding_struct::padding_struct; +use zerocopy::*; + +#[repr(C)] +#[padding_struct] +#[derive(Clone, Copy, FromBytes, IntoBytes, Immutable, KnownLayout)] +struct SafeStruct { + field1: u32, + field2: u64, +} + +// Now you can safely cast to/from bytes +let s = SafeStruct::new_zeroed(); +let bytes: &[u8] = s.as_bytes(); +assert_eq!(bytes.len(), size_of::()); +assert_eq!(bytes, &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); +``` + +## License + +This project is licensed under MPL-2.0. + + +[`zerocopy`]: https://docs.rs/zerocopy/ diff --git a/ostd/libs/padding-struct/src/lib.rs b/ostd/libs/padding-struct/src/lib.rs new file mode 100644 index 000000000..1bc7040d9 --- /dev/null +++ b/ostd/libs/padding-struct/src/lib.rs @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: MPL-2.0 + +#![doc = include_str!("../README.md")] + +use proc_macro::TokenStream; +use quote::quote; +use syn::{ + Attribute, Data, DataStruct, DeriveInput, Fields, Ident, Token, parse_macro_input, + punctuated::Punctuated, spanned::Spanned, +}; + +/// Checks if the struct has a `#[repr(C)]` attribute (possibly with other `repr` options) +fn has_repr_c(attrs: &[Attribute]) -> bool { + attrs.iter().any(|attr| { + if attr.path().is_ident("repr") { + // Parse the attribute using a custom parser + let result = attr.parse_args_with(Punctuated::::parse_terminated); + + if let Ok(list) = result { + return list + .iter() + .any(|meta| matches!(meta, syn::Meta::Path(path) if path.is_ident("C"))); + } + + false + } else { + false + } + }) +} + +/// Extracts all `#[repr(...)]` attributes from the given attributes +fn extract_repr_attrs(attrs: &[Attribute]) -> Vec<&Attribute> { + attrs + .iter() + .filter(|attr| attr.path().is_ident("repr")) + .collect() +} + +/// Procedural macro to automatically add padding fields to a `#[repr(C)]` struct. +/// +/// This macro generates two structs: +/// 1. A reference struct (prefixed and suffixed with `__`) containing the original fields without padding. +/// 2. A padded struct (using the original name) with `__padN` padding fields after each original field. +/// +/// # Padding Calculation Rules +/// +/// - For non-last fields: padding size = next field's offset - current field's offset - current field's size. +/// - For the last field: padding size = struct total size - current field's offset - current field's size. +/// +/// # Requirements +/// +/// - The struct must have a `#[repr(C)]` attribute. +/// - The struct must have named fields. +/// +/// # Examples +/// +/// ```rust +/// use padding_struct::padding_struct; +/// +/// #[repr(C)] +/// #[padding_struct] +/// struct MyStruct { +/// a: u8, +/// b: u32, +/// } +/// ``` +/// +/// This generates code equivalent to: +/// +/// ```rust +/// use core::mem::offset_of; +/// +/// #[repr(C)] +/// struct __MyStruct__ { +/// a: u8, +/// b: u32, +/// } +/// +/// #[repr(C)] +/// struct MyStruct { +/// a: u8, +/// pub __pad1: +/// [u8; const { offset_of!(__MyStruct__, b) - offset_of!(__MyStruct__, a) - size_of::() }], +/// b: u32, +/// pub __pad2: +/// [u8; const { size_of::<__MyStruct__>() - offset_of!(__MyStruct__, b) - size_of::() }], +/// } +/// ``` +#[proc_macro_attribute] +pub fn padding_struct(args: TokenStream, input: TokenStream) -> TokenStream { + // Reject any provided arguments to the attribute + if !args.is_empty() { + panic!("`#[padding_struct]` does not accept any arguments"); + } + let input = parse_macro_input!(input as DeriveInput); + + // Ensure it's a struct + let fields = match &input.data { + Data::Struct(DataStruct { + fields: Fields::Named(fields), + .. + }) => &fields.named, + _ => panic!("`#[padding_struct]` only supports named-field structs"), + }; + + // Ensure #[repr(C)] is present + if !has_repr_c(&input.attrs) { + panic!("`#[padding_struct]` requires `#[repr(C)]` or `#[repr(C, ...)]` on struct"); + } + + let name = &input.ident; + let vis = &input.vis; + let generics = &input.generics; + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let ref_name = Ident::new(&format!("__{}__", name), name.span()); + + // Extract all repr attributes for the reference struct + let repr_attrs = extract_repr_attrs(&input.attrs); + + // Generate reference struct (same fields, no padding, with all repr attributes) + let ref_fields: Vec<_> = fields.iter().collect(); + let ref_struct = quote! { + #(#repr_attrs)* + #[allow(missing_docs)] + #[doc(hidden)] + struct #ref_name #impl_generics #where_clause { + #(#ref_fields),* + } + }; + + // Filter attributes for the padded struct (keep all except #[padding_struct]) + let padded_attrs: Vec<_> = input + .attrs + .iter() + .filter(|attr| !attr.path().is_ident("padding_struct")) + .collect(); + + // Generate padded struct with inline const expressions + let mut padded_fields = Vec::new(); + let field_vec: Vec<_> = fields.iter().collect(); + + for (i, field) in field_vec.iter().enumerate() { + let field_name = &field.ident; + let field_ty = &field.ty; + let field_attrs = &field.attrs; + let field_vis = &field.vis; + + // Add original field with its attributes and comments + padded_fields.push(quote! { + #(#field_attrs)* + #field_vis #field_name: #field_ty + }); + + // Generate padding field with inline const expression + let pad_num = i + 1; + let pad_ident = Ident::new(&format!("__pad{}", pad_num), field.span()); + + let pad_size_expr = if i == field_vec.len() - 1 { + // Last field: padding to end of struct + quote! { + ::core::mem::size_of::<#ref_name #ty_generics>() + - ::core::mem::offset_of!(#ref_name #ty_generics, #field_name) + - ::core::mem::size_of::<#field_ty>() + } + } else { + // Middle field: padding to next field + let next_field = field_vec[i + 1]; + let next_field_name = &next_field.ident; + quote! { + ::core::mem::offset_of!(#ref_name #ty_generics, #next_field_name) + - ::core::mem::offset_of!(#ref_name #ty_generics, #field_name) + - ::core::mem::size_of::<#field_ty>() + } + }; + + // Add padding field with inline const block + padded_fields.push(quote! { + #[allow(missing_docs)] + pub #pad_ident: [u8; { #pad_size_expr }] + }); + } + + let padded_struct = quote! { + #(#padded_attrs)* + #vis struct #name #impl_generics #where_clause { + #(#padded_fields),* + } + }; + + // Generate compile-time assertions to ensure size and alignment match + let size_align_check = quote! { + const _: () = { + // Assert that sizes are equal + const _: [(); ::core::mem::size_of::<#ref_name #ty_generics>()] = + [(); ::core::mem::size_of::<#name #ty_generics>()]; + + // Assert that alignments are equal + const _: [(); ::core::mem::align_of::<#ref_name #ty_generics>()] = + [(); ::core::mem::align_of::<#name #ty_generics>()]; + }; + }; + + let expanded = quote! { + #ref_struct + + #padded_struct + + #size_align_check + }; + + TokenStream::from(expanded) +} diff --git a/ostd/libs/padding-struct/tests/integration_test.rs b/ostd/libs/padding-struct/tests/integration_test.rs new file mode 100644 index 000000000..6e9238a75 --- /dev/null +++ b/ostd/libs/padding-struct/tests/integration_test.rs @@ -0,0 +1,267 @@ +// SPDX-License-Identifier: MPL-2.0 + +use core::mem::offset_of; + +use padding_struct::padding_struct; + +/// Test basic padding functionality +#[test] +fn basic_padding() { + #[repr(C)] + #[padding_struct] + struct TestStruct { + a: u8, + b: u32, + c: u16, + } + + // Verify reference struct exists + let _ref_struct = __TestStruct__ { a: 1, b: 2, c: 3 }; + + // Verify padded struct + let padded = TestStruct { + a: 1, + __pad1: [0; { + offset_of!(__TestStruct__, b) - offset_of!(__TestStruct__, a) - size_of::() + }], + b: 2, + __pad2: [0; { + offset_of!(__TestStruct__, c) - offset_of!(__TestStruct__, b) - size_of::() + }], + c: 3, + __pad3: [0; { + size_of::<__TestStruct__>() - offset_of!(__TestStruct__, c) - size_of::() + }], + }; + + assert_eq!(padded.a, 1); + assert_eq!(padded.b, 2); + assert_eq!(padded.c, 3); +} + +/// Test single field struct +#[test] +fn single_field() { + #[repr(C)] + #[padding_struct] + struct SingleField { + value: u64, + } + + let single = SingleField { + value: 42, + __pad1: [0; { + size_of::<__SingleField__>() - offset_of!(__SingleField__, value) - size_of::() + }], + }; + + assert_eq!(single.value, 42); +} + +/// Test multiple fields struct +#[test] +fn multiple_fields() { + #[repr(C)] + #[padding_struct] + struct MultiField { + a: u8, + b: u16, + c: u32, + d: u64, + } + + let multi = MultiField { + a: 1, + __pad1: [0; { + offset_of!(__MultiField__, b) - offset_of!(__MultiField__, a) - size_of::() + }], + b: 2, + __pad2: [0; { + offset_of!(__MultiField__, c) - offset_of!(__MultiField__, b) - size_of::() + }], + c: 3, + __pad3: [0; { + offset_of!(__MultiField__, d) - offset_of!(__MultiField__, c) - size_of::() + }], + d: 4, + __pad4: [0; { + size_of::<__MultiField__>() - offset_of!(__MultiField__, d) - size_of::() + }], + }; + + assert_eq!(multi.a, 1); + assert_eq!(multi.b, 2); + assert_eq!(multi.c, 3); + assert_eq!(multi.d, 4); +} + +/// Test struct with field documentation +#[test] +fn with_field_docs() { + #[repr(C)] + #[padding_struct] + struct Documented { + /// First field + first: u8, + /// Second field + second: u32, + } + + let doc = Documented { + first: 10, + __pad1: [0; { + offset_of!(__Documented__, second) - offset_of!(__Documented__, first) - size_of::() + }], + second: 20, + __pad2: [0; { + size_of::<__Documented__>() - offset_of!(__Documented__, second) - size_of::() + }], + }; + + assert_eq!(doc.first, 10); + assert_eq!(doc.second, 20); +} + +/// Verify that field offsets are consistent between reference and padded structs +#[test] +fn offset_consistency() { + #[repr(C)] + #[padding_struct] + struct OffsetTest { + a: u8, + b: u32, + } + + // Reference struct offsets + let ref_a_offset = offset_of!(__OffsetTest__, a); + let ref_b_offset = offset_of!(__OffsetTest__, b); + + // Padded struct offsets should be the same + let padded_a_offset = offset_of!(OffsetTest, a); + let padded_b_offset = offset_of!(OffsetTest, b); + + assert_eq!(ref_a_offset, padded_a_offset); + assert_eq!(ref_b_offset, padded_b_offset); +} + +/// Test that padding is zero-filled +#[test] +fn padding_zero_filled() { + #[repr(C)] + #[padding_struct] + struct ZeroPadded { + a: u8, + b: u32, + } + + let zero_pad = ZeroPadded { + a: 255, + __pad1: [0; { + offset_of!(__ZeroPadded__, b) - offset_of!(__ZeroPadded__, a) - size_of::() + }], + b: 0xFFFFFFFF, + __pad2: [0; { + size_of::<__ZeroPadded__>() - offset_of!(__ZeroPadded__, b) - size_of::() + }], + }; + + // Verify padding is all zeros + for byte in &zero_pad.__pad1 { + assert_eq!(*byte, 0); + } + for byte in &zero_pad.__pad2 { + assert_eq!(*byte, 0); + } +} + +/// Test that padded struct can derive zerocopy traits +#[test] +fn zerocopy_derive() { + use zerocopy::*; + + #[repr(C)] + #[padding_struct] + #[derive(Clone, Copy, FromBytes, IntoBytes, Immutable, KnownLayout)] + struct ZerocopyStruct { + a: u8, + b: u32, + c: u16, + } + + // Test Zeroable + let zeroed = ZerocopyStruct::new_zeroed(); + assert_eq!(zeroed.a, 0); + assert_eq!(zeroed.b, 0); + assert_eq!(zeroed.c, 0); + + // Test Pod - cast from bytes + let bytes = [1u8, 0, 0, 0, 0x12, 0x34, 0x56, 0x78, 0xAB, 0xCD, 0, 0]; + let from_bytes: &ZerocopyStruct = + FromBytes::ref_from_bytes(&bytes[..size_of::()]).unwrap(); + assert_eq!(from_bytes.a, 1); + assert_eq!(from_bytes.b, 0x78563412); + assert_eq!(from_bytes.c, 0xCDAB); + + // Test Pod - cast to bytes + let test_struct = ZerocopyStruct { + a: 42, + __pad1: [0; { + offset_of!(__ZerocopyStruct__, b) - offset_of!(__ZerocopyStruct__, a) - size_of::() + }], + b: 0xDEADBEEF, + __pad2: [0; { + offset_of!(__ZerocopyStruct__, c) - offset_of!(__ZerocopyStruct__, b) - size_of::() + }], + c: 0x1234, + __pad3: [0; { + size_of::<__ZerocopyStruct__>() - offset_of!(__ZerocopyStruct__, c) - size_of::() + }], + }; + let as_bytes: &[u8] = test_struct.as_bytes(); + assert_eq!(as_bytes[0], 42); +} + +/// Test that repr attributes (align, packed, etc.) are preserved in ref struct +#[test] +fn repr_align_preserved() { + #[repr(C, align(16))] + #[padding_struct] + struct AlignedStruct { + a: u8, + b: u32, + } + + // Verify alignment is correct + assert_eq!(align_of::(), 16); + assert_eq!(align_of::<__AlignedStruct__>(), 16); + + let aligned = AlignedStruct { + a: 1, + __pad1: [0; { + offset_of!(__AlignedStruct__, b) - offset_of!(__AlignedStruct__, a) - size_of::() + }], + b: 2, + __pad2: [0; { + size_of::<__AlignedStruct__>() - offset_of!(__AlignedStruct__, b) - size_of::() + }], + }; + + assert_eq!(aligned.a, 1); + assert_eq!(aligned.b, 2); +} + +/// Test that size and alignment match between ref struct and padded struct +#[test] +fn size_align_match() { + #[repr(C, align(8))] + #[padding_struct] + struct TestStruct { + a: u8, + b: u16, + c: u32, + } + + // The compile-time check ensures these are equal + assert_eq!(size_of::(), size_of::<__TestStruct__>()); + assert_eq!(align_of::(), align_of::<__TestStruct__>()); +}