From c8f2cfaeae6e6fec84ee7a583473b9243a4e8733 Mon Sep 17 00:00:00 2001 From: jiangjianfeng Date: Mon, 26 Jan 2026 08:31:35 +0000 Subject: [PATCH] Add ostd-pod crate and #[derive(pod)], pod_union macros --- Cargo.lock | 29 ++- Cargo.toml | 3 + Makefile | 2 + ostd/libs/ostd-pod/Cargo.toml | 24 ++ ostd/libs/ostd-pod/README.md | 175 ++++++++++++++ ostd/libs/ostd-pod/macros/Cargo.toml | 25 ++ ostd/libs/ostd-pod/macros/README.md | 53 ++++ ostd/libs/ostd-pod/macros/src/lib.rs | 92 +++++++ ostd/libs/ostd-pod/macros/src/pod_derive.rs | 52 ++++ ostd/libs/ostd-pod/macros/src/pod_union.rs | 255 ++++++++++++++++++++ ostd/libs/ostd-pod/src/array_helper.rs | 184 ++++++++++++++ ostd/libs/ostd-pod/src/lib.rs | 50 ++++ ostd/libs/ostd-pod/tests/derive_test.rs | 55 +++++ ostd/libs/ostd-pod/tests/union_test.rs | 54 +++++ ostd/libs/padding-struct/README.md | 2 +- 15 files changed, 1049 insertions(+), 6 deletions(-) create mode 100644 ostd/libs/ostd-pod/Cargo.toml create mode 100644 ostd/libs/ostd-pod/README.md create mode 100644 ostd/libs/ostd-pod/macros/Cargo.toml create mode 100644 ostd/libs/ostd-pod/macros/README.md create mode 100644 ostd/libs/ostd-pod/macros/src/lib.rs create mode 100644 ostd/libs/ostd-pod/macros/src/pod_derive.rs create mode 100644 ostd/libs/ostd-pod/macros/src/pod_union.rs create mode 100644 ostd/libs/ostd-pod/src/array_helper.rs create mode 100644 ostd/libs/ostd-pod/src/lib.rs create mode 100644 ostd/libs/ostd-pod/tests/derive_test.rs create mode 100644 ostd/libs/ostd-pod/tests/union_test.rs diff --git a/Cargo.lock b/Cargo.lock index 2a96163be..c1a52a15c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -361,6 +361,7 @@ dependencies = [ "inherit-methods-macro", "osdk-heap-allocator", "ostd", + "ostd-pod", "typeflags-util", ] @@ -1350,7 +1351,7 @@ dependencies = [ "multiboot2", "num-traits", "ostd-macros", - "ostd-pod", + "ostd-pod 0.1.1", "ostd-test", "riscv", "sbi-rt", @@ -1381,6 +1382,15 @@ dependencies = [ "ostd-pod-derive", ] +[[package]] +name = "ostd-pod" +version = "0.1.2" +dependencies = [ + "ostd-pod-macros", + "padding-struct", + "zerocopy", +] + [[package]] name = "ostd-pod-derive" version = "0.1.1" @@ -1391,6 +1401,15 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ostd-pod-macros" +version = "0.1.2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "ostd-test" version = "0.17.0" @@ -2193,18 +2212,18 @@ checksum = "2fe21bcc34ca7fe6dd56cc2cb1261ea59d6b93620215aefb5ea6032265527784" [[package]] name = "zerocopy" -version = "0.8.25" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +checksum = "71ddd76bcebeed25db614f82bf31a9f4222d3fbba300e6fb6c00afa26cbd4d9d" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.25" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +checksum = "d8187381b52e32220d50b255276aa16a084ec0a9017a0ca2152a1f55c539758d" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 81d293aa9..486523b74 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,8 @@ members = [ "ostd/libs/linux-bzimage/boot-params", "ostd/libs/linux-bzimage/builder", "ostd/libs/linux-bzimage/setup", + "ostd/libs/ostd-pod", + "ostd/libs/ostd-pod/macros", "ostd/libs/ostd-macros", "ostd/libs/ostd-test", "ostd/libs/padding-struct", @@ -142,6 +144,7 @@ serde = { version = "1.0.192", default-features = false, features = ["alloc", "d smallvec = "1.13.2" uart_16550 = "0.3.0" volatile = "0.6.1" +zerocopy = { version = "0.8.34", features = [ "derive" ] } # External dependencies only for safe crates (i.e., crates under kernel or osdk/deps directories) # diff --git a/Makefile b/Makefile index 08b61bda9..64e5223fb 100644 --- a/Makefile +++ b/Makefile @@ -212,6 +212,8 @@ NON_OSDK_CRATES := \ ostd/libs/linux-bzimage/boot-params \ ostd/libs/linux-bzimage/builder \ ostd/libs/ostd-macros \ + ostd/libs/ostd-pod \ + ostd/libs/ostd-pod/macros \ ostd/libs/ostd-test \ ostd/libs/padding-struct \ kernel/libs/aster-rights \ diff --git a/ostd/libs/ostd-pod/Cargo.toml b/ostd/libs/ostd-pod/Cargo.toml new file mode 100644 index 000000000..8159273ba --- /dev/null +++ b/ostd/libs/ostd-pod/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "ostd-pod" +# REMINDER: Whenever you change this number, +# update the external documentation links in README.md. +version = "0.4.0" +description = "A trait for plain old data (POD)" +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 + +[dependencies] +ostd-pod-macros = { path = "macros", version = "0.4.0", optional = true } +padding-struct = { path = "../padding-struct", version = "0.2.0", optional = true } +zerocopy.workspace = true + +[features] +default = ["macros"] +macros = ["ostd-pod-macros", "padding-struct"] + +[lints] +workspace = true diff --git a/ostd/libs/ostd-pod/README.md b/ostd/libs/ostd-pod/README.md new file mode 100644 index 000000000..68942b820 --- /dev/null +++ b/ostd/libs/ostd-pod/README.md @@ -0,0 +1,175 @@ + + +# ostd-pod + +A trait and macros for Plain Old Data (POD) types. + +This crate provides the [`Pod`] trait, +which marks types that can be safely converted to and from arbitrary byte sequences. +It's built on top of the mature [zerocopy] crate to ensure type safety. + +## Features + +- **Safe Byte Conversion**: POD types can be safely converted to byte sequences and created from + byte sequences. +- **Based on zerocopy**: Built on top of the [zerocopy] crate for type safety guarantees. +- **Derive Macro Support**: Provides `#[derive(Pod)]` to simplify POD type definitions. +- **Union Support**: Supports union types via the `#[pod_union]` macro. +- **Automatic Padding Management**: Automatically handles padding bytes through the + `#[padding_struct]` macro. + +## What is a POD Type? + +A POD (Plain Old Data) type is a type +that can be safely converted to and from an arbitrary byte sequence. +For example, primitive types like `u8` and `i16` are POD types; +yet, `bool` is not a POD type. +A struct whose fields are POD types is also considered a POD. +A union whose fields are all POD types is also a POD. +The memory layout of any POD type is `#[repr(C)]`. + +## Quick Start + +### Step 1: Edit your `Cargo.toml` + +Add these dependencies to your `Cargo.toml`. + +```toml +[dependencies] +ostd-pod = "0.4.0" +zerocopy = { version = "0.8.34", features = ["derive" ] } +``` + +`zerocopy` must be explicitly specified as a dependency +because `ostd-pod` relies on its procedural macros, +which expand to compile-time checks that reference internal `zerocopy` +types hardcoded to the `zerocopy` crate name. + +### Step 2: Edit your `lib.rs` (or `main.rs`) + +Insert the following lines to your `lib.rs` or `main.rs`: + +```rust +#[macro_use] +extern crate ostd_pod; +``` + +We import the `ostd_pod` crate with `extern` and `#[macro_use]` +for the convenience of having Rust's built-in `derive` attribute macro +globally overridden by the custom `derive` attribute macro provided by this crate. +This custom `derive` macro is needed +because the `Pod` trait cannot be derived in the regular way as other traits. + +### Step 3: Define your first POD type + +Now we can define a POD struct that +can be converted to and from any byte sequence of the same size. + +```rust +#[macro_use] +extern crate ostd_pod; +use ostd_pod::{IntoBytes, FromBytes, Pod}; + +#[repr(C)] +#[derive(Pod, Clone, Copy, Debug, PartialEq)] +struct Point { + x: i32, + y: i32, +} + +fn main() { + let point = Point { x: 10, y: 20 }; + + // Convert to bytes + let bytes = point.as_bytes(); + assert_eq!(bytes, &[10, 0, 0, 0, 20, 0, 0, 0]); + + // Create from bytes + let point2 = Point::from_bytes(bytes); + assert_eq!(point, point2); +} +``` + +## Advanced Usage + +### Use POD Unions + +Union fields cannot be accessed safely because we cannot know which variant is currently active. +To address this, we provide a [`pod_union`] macro +that enables safe access to union fields. + +```rust +#[macro_use] +extern crate ostd_pod; +use ostd_pod::{FromZeros, IntoBytes}; + +#[pod_union] +#[derive(Copy, Clone)] +#[repr(C)] +union Data { + value: u64, + bytes: [u8; 4], +} + +fn main() { + let mut data = Data::new_value(0x1234567890ABCDEF); + + // Access the same memory through different fields + assert_eq!(*data.value(), 0x1234567890ABCDEF); + assert_eq!(*data.bytes(), [0xEF, 0xCD, 0xAB, 0x90]); +} +``` + +### Automatic Padding Handling + +When a struct has fields with different sizes, +there may be implicit padding bytes between fields. +The [`padding_struct`] macro automatically inserts explicit padding fields +so the struct can be safely used as a POD type. + +```rust +#[macro_use] +extern crate ostd_pod; +use ostd_pod::IntoBytes; + +#[repr(C)] +#[padding_struct] +#[derive(Pod, Clone, Copy, Debug, Default)] +struct PackedData { + a: u8, + // `padding_struct` automatically inserts 3 bytes of padding here + b: u32, + c: u16, + // `padding_struct` automatically inserts 2 bytes of padding here +} + +fn main() { + let data = PackedData { + a: 1, + b: 2, + c: 3, + ..Default::default() + }; + + // Can safely convert to bytes, padding bytes are explicitly handled + let bytes = data.as_bytes(); + assert_eq!(bytes.len(), 12); + assert_eq!(bytes, [1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0]); +} +``` + +## License + +This project is licensed under MPL-2.0. + + +[`Pod`]: https://docs.rs/ostd-pod/0.4.0/trait.Pod.html +[`padding_struct`]: https://docs.rs/ostd-pod/0.4.0/attr.padding_struct.html +[`pod_union`]: https://docs.rs/ostd-pod/0.4.0/attr.pod_union.html +[zerocopy]: https://docs.rs/zerocopy/ \ No newline at end of file diff --git a/ostd/libs/ostd-pod/macros/Cargo.toml b/ostd/libs/ostd-pod/macros/Cargo.toml new file mode 100644 index 000000000..14e4cda1c --- /dev/null +++ b/ostd/libs/ostd-pod/macros/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "ostd-pod-macros" +version = "0.4.0" +description = "The proc macro crate for ostd-pod" +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] +ostd-pod = { path = "../", default-features = false } +zerocopy.workspace = true + +[lints] +workspace = true diff --git a/ostd/libs/ostd-pod/macros/README.md b/ostd/libs/ostd-pod/macros/README.md new file mode 100644 index 000000000..42fe8ce9f --- /dev/null +++ b/ostd/libs/ostd-pod/macros/README.md @@ -0,0 +1,53 @@ + + +# ostd-pod-macros + +Procedural macros for the [ostd-pod] crate. + +This crate provides procedural macros to simplify working with Plain Old Data (POD) types. +It exports two main macros: + +- `#[derive(Pod)]`: An attribute macro that expands into the underlying `zerocopy` traits +- `#[pod_union]`: An attribute macro that makes unions safe to use as POD types + +## The `derive` Macro + +The `#[derive(Pod)]` macro is a convenience wrapper that automatically derives the required [zerocopy] traits for POD types. + +Unlike typical derive procedural macros, `derive` in this crate is actually an **attribute** macro that works by shadowing [`::core::prelude::v1::derive`]. + +## The `pod_union` Macro + +The `#[pod_union]` attribute macro enables safe usage of unions as POD types. It automatically: + +- Derives the necessary [zerocopy] traits +- Generates safe initializer and accessor methods for each union field +- Enforces `#[repr(C)]` layout +- Ensures all fields are POD types + +### Generated Initializer and Accessor Methods + +For each field `foo` in the union, the macro generates: + +- `fn new_foo(value: FieldType) -> Self`: Constructs an instance from the field +- `fn foo(&self) -> &FieldType`: Returns a reference to the field +- `fn foo_mut(&mut self) -> &mut FieldType`: Returns a mutable reference to the field + +These methods use `zerocopy`'s safe byte conversion methods, avoiding unsafe code. + +For detailed usage examples, see the crate [ostd-pod] documentation. + +## License + +This project is licensed under MPL-2.0. + + +[ostd-pod]: https://docs.rs/ostd-pod/ +[zerocopy]: https://docs.rs/zerocopy/ +[`::core::prelude::v1::derive`]: https://doc.rust-lang.org/core/prelude/v1/attr.derive.html diff --git a/ostd/libs/ostd-pod/macros/src/lib.rs b/ostd/libs/ostd-pod/macros/src/lib.rs new file mode 100644 index 000000000..5ceac7320 --- /dev/null +++ b/ostd/libs/ostd-pod/macros/src/lib.rs @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: MPL-2.0 + +#![doc = include_str!("../README.md")] + +use proc_macro::TokenStream; + +mod pod_derive; +mod pod_union; + +/// An attribute macro that replaces `#[derive(Pod)]` with the corresponding zerocopy traits. +#[proc_macro_attribute] +pub fn derive(attrs: TokenStream, input: TokenStream) -> TokenStream { + pod_derive::expand_derive(attrs, input) +} + +/// An attribute macro that enables safe usage of unions as POD types. +/// +/// Rust's built-in unions cannot directly derive `zerocopy::IntoBytes` because unions require +/// field-by-field initialization and access. The `#[pod_union]` macro solves this by +/// transforming a union into a safe wrapper struct. +/// +/// # Implementation details +/// +/// When you write: +/// +/// ```rust +/// use ostd_pod_macros::pod_union; +/// +/// #[repr(C)] +/// #[pod_union] +/// #[derive(Clone, Copy)] +/// pub union Data { +/// value: u64, +/// bytes: [u8; 4], +/// } +/// ``` +/// +/// The `#[pod_union]` macro internally generates something equivalent to: +/// +/// ```rust +/// use ostd_pod::array_helper::{ArrayFactory, ArrayManufacture, U64Array}; +/// use ostd_pod::{FromBytes, FromZeros, Immutable, IntoBytes, KnownLayout, Pod}; +/// +/// // Internal private union +/// #[repr(C)] +/// #[derive(FromBytes, KnownLayout, Immutable)] +/// union __Data__ { +/// value: u64, +/// bytes: [u8; 4], +/// } +/// +/// // Public wrapper struct that provides safe access +/// #[repr(transparent)] +/// #[derive(FromBytes, KnownLayout, Immutable, IntoBytes)] +/// pub struct Data(() }, +/// { size_of::<__Data__>() / (align_of::<__Data__>()) }, +/// > as ArrayManufacture>::Array); +/// +/// impl Data { +/// // Field accessor methods +/// pub fn value(&self) -> &u64 { +/// u64::ref_from_bytes(&self.0.as_bytes()[..8]).unwrap() +/// } +/// pub fn value_mut(&mut self) -> &mut u64 { +/// u64::mut_from_bytes(&mut self.0.as_mut_bytes()[..8]).unwrap() +/// } +/// pub fn bytes(&self) -> &[u8; 4] { +/// <[u8; 4]>::ref_from_bytes(&self.0.as_bytes()[..4]).unwrap() +/// } +/// pub fn bytes_mut(&mut self) -> &mut [u8; 4] { +/// <[u8; 4]>::mut_from_bytes(&mut self.0.as_mut_bytes()[..4]).unwrap() +/// } +/// +/// // Initializer methods +/// pub fn new_value(value: u64) -> Self { +/// let mut slf = Self::new_zeroed(); +/// *slf.value_mut() = value; +/// slf +/// } +/// pub fn new_bytes(bytes: [u8; 4]) -> Self { +/// let mut slf = Self::new_zeroed(); +/// *slf.bytes_mut() = bytes; +/// slf +/// } +/// } +/// ``` +#[proc_macro_attribute] +pub fn pod_union(_attr: TokenStream, item: TokenStream) -> TokenStream { + let input = syn::parse_macro_input!(item as syn::DeriveInput); + pod_union::expand_pod_union(input).into() +} diff --git a/ostd/libs/ostd-pod/macros/src/pod_derive.rs b/ostd/libs/ostd-pod/macros/src/pod_derive.rs new file mode 100644 index 000000000..5e06bddc0 --- /dev/null +++ b/ostd/libs/ostd-pod/macros/src/pod_derive.rs @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MPL-2.0 + +use proc_macro::TokenStream; +use quote::quote; + +fn push_zerocopy_derive( + derives: &mut Vec, + ident: &str, + trailing_comma: bool, +) { + use proc_macro2::{Ident, Punct, Spacing, Span, TokenTree}; + + derives.push(TokenTree::Punct(Punct::new(':', Spacing::Joint))); + derives.push(TokenTree::Punct(Punct::new(':', Spacing::Alone))); + derives.push(TokenTree::Ident(Ident::new("zerocopy", Span::call_site()))); + derives.push(TokenTree::Punct(Punct::new(':', Spacing::Joint))); + derives.push(TokenTree::Punct(Punct::new(':', Spacing::Alone))); + derives.push(TokenTree::Ident(Ident::new(ident, Span::call_site()))); + if trailing_comma { + derives.push(TokenTree::Punct(Punct::new(',', Spacing::Alone))); + } +} + +pub fn expand_derive(attrs: TokenStream, input: TokenStream) -> TokenStream { + use proc_macro2::TokenTree; + + // Process the derive attributes + let mut new_derives = Vec::new(); + let attr_tokens = proc_macro2::TokenStream::from(attrs); + for token in attr_tokens.into_iter() { + match token { + TokenTree::Ident(ident) if ident.to_string() == "Pod" => { + // Replace Pod with zerocopy traits + push_zerocopy_derive(&mut new_derives, "FromBytes", true); + push_zerocopy_derive(&mut new_derives, "IntoBytes", true); + push_zerocopy_derive(&mut new_derives, "Immutable", true); + push_zerocopy_derive(&mut new_derives, "KnownLayout", false); + } + _ => { + new_derives.push(token); + } + } + } + + // Build the output: #[::core::prelude::v1::derive(...)] + input + let input2: proc_macro2::TokenStream = input.into(); + quote!( + #[::core::prelude::v1::derive(#(#new_derives)*)] + #input2 + ) + .into() +} diff --git a/ostd/libs/ostd-pod/macros/src/pod_union.rs b/ostd/libs/ostd-pod/macros/src/pod_union.rs new file mode 100644 index 000000000..b6c100970 --- /dev/null +++ b/ostd/libs/ostd-pod/macros/src/pod_union.rs @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: MPL-2.0 + +use proc_macro2::TokenStream as TokenStream2; +use quote::{ToTokens, quote}; +use syn::{ + Attribute, Data, DeriveInput, Ident, Path, Token, Visibility, parse_quote, + punctuated::Punctuated, spanned::Spanned, +}; + +const DERIVE_IDENT: &str = "derive"; +const REPR_IDENT: &str = "repr"; + +/// Splits attributes into non-derive attributes and derive paths +fn split_attrs(attrs: Vec) -> (Vec, Vec) { + let mut other_attrs = Vec::new(); + let mut derive_paths = Vec::new(); + + for attr in attrs { + if attr.path().is_ident(DERIVE_IDENT) { + let parsed: Punctuated = attr + .parse_args_with(Punctuated::parse_terminated) + .expect("failed to parse derive attribute"); + derive_paths.extend(parsed.into_iter()); + } else { + other_attrs.push(attr); + } + } + + (other_attrs, derive_paths) +} + +/// Checks if the attributes contain `#[repr(C)]` +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 + } + }) +} + +/// Inserts a path into the vector if it's not already present +fn insert_if_absent(paths: &mut Vec, new_path: Path) { + let new_repr = new_path.to_token_stream().to_string(); + if !paths + .iter() + .any(|path| path.to_token_stream().to_string() == new_repr) + { + paths.push(new_path); + } +} + +pub fn expand_pod_union(input: DeriveInput) -> TokenStream2 { + if !has_repr_c(&input.attrs) { + panic!("`#[pod_union]` requires `#[repr(C)]` or `#[repr(C, ...)]` on unions"); + } + + let data_union = match input.data { + Data::Union(ref u) => u, + _ => panic!("`#[pod_union]` can only be used on unions"), + }; + + let vis: Visibility = input.vis.clone(); + let ident = &input.ident; + let internal_ident = Ident::new(&format!("__{}__", ident), ident.span()); + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + + // Split attributes: keep non-derive attrs, collect derive paths + let (other_attrs, derive_paths) = split_attrs(input.attrs.clone()); + + let mut union_derive_paths = derive_paths.clone(); + let mut struct_derive_paths = derive_paths; + + // Add required zerocopy derives for internal union + insert_if_absent(&mut union_derive_paths, parse_quote!(::zerocopy::FromBytes)); + insert_if_absent(&mut union_derive_paths, parse_quote!(::zerocopy::Immutable)); + insert_if_absent( + &mut union_derive_paths, + parse_quote!(::zerocopy::KnownLayout), + ); + + // Add required zerocopy derives for public struct wrapper + insert_if_absent( + &mut struct_derive_paths, + parse_quote!(::zerocopy::FromBytes), + ); + insert_if_absent( + &mut struct_derive_paths, + parse_quote!(::zerocopy::Immutable), + ); + insert_if_absent( + &mut struct_derive_paths, + parse_quote!(::zerocopy::IntoBytes), + ); + insert_if_absent( + &mut struct_derive_paths, + parse_quote!(::zerocopy::KnownLayout), + ); + + let union_derive_attr: Attribute = parse_quote! { + #[derive(#(#union_derive_paths),*)] + }; + + let struct_derive_attr: Attribute = parse_quote! { + #[derive(#(#struct_derive_paths),*)] + }; + + let mut union_attrs = other_attrs.clone(); + union_attrs.push(union_derive_attr); + + let mut struct_attrs: Vec = other_attrs + .into_iter() + .filter(|attr| !attr.path().is_ident(REPR_IDENT)) + .collect(); + struct_attrs.push(parse_quote!(#[repr(transparent)])); + struct_attrs.push(struct_derive_attr); + + let mut internal_union = input.clone(); + internal_union.ident = internal_ident.clone(); + internal_union.vis = Visibility::Inherited; + internal_union.attrs = union_attrs; + + // Generate accessor methods for each field + let accessor_methods = data_union.fields.named.iter().map(|field| { + let field_name = &field.ident; + let field_ty = &field.ty; + + let ref_method_name = field_name; + let mut_method_name = syn::Ident::new( + &format!("{}_mut", field_name.as_ref().unwrap()), + field_name.span(), + ); + + quote! { + pub fn #ref_method_name(&self) -> &#field_ty { + use ::zerocopy::IntoBytes; + let bytes = self.0.as_bytes(); + let slice = &bytes[..::core::mem::size_of::<#field_ty>()]; + <#field_ty as ::zerocopy::FromBytes>::ref_from_bytes(slice).unwrap() + } + + pub fn #mut_method_name(&mut self) -> &mut #field_ty { + use ::zerocopy::IntoBytes; + let bytes = self.0.as_mut_bytes(); + let slice = &mut bytes[..::core::mem::size_of::<#field_ty>()]; + <#field_ty as ::zerocopy::FromBytes>::mut_from_bytes(slice).unwrap() + } + } + }); + + // Generate initializer methods for each field + let init_methods = data_union.fields.named.iter().map(|field| { + let field_name = field.ident.as_ref().expect("field name"); + let field_ty = &field.ty; + let new_method_name = syn::Ident::new(&format!("new_{}", field_name), field_name.span()); + let mut_method_name = syn::Ident::new(&format!("{}_mut", field_name), field_name.span()); + + quote! { + #[allow(non_snake_case)] + pub fn #new_method_name(value: #field_ty) -> Self { + use ::zerocopy::FromZeros; + let mut slf = Self::new_zeroed(); + *slf.#mut_method_name() = value; + slf + } + } + }); + + // Generate module name to avoid symbol conflicts + let module_ident = syn::Ident::new( + &format!( + "__private_module_generated_by_ostd_pod_{}", + ident.to_string().to_lowercase() + ), + proc_macro2::Span::call_site(), + ); + + // Add Copy constraint compile-time assertion + let copy_assert = quote! { + const _: () = { + fn assert_copy() {} + fn assert_union_copy #impl_generics() #where_clause { + assert_copy::<#ident #ty_generics>(); + } + }; + }; + + // Generate Pod constraint assertions for all fields + let field_pod_asserts = data_union.fields.named.iter().map(|field| { + let ty = &field.ty; + quote! { + assert_pod::<#ty>(); + } + }); + let pod_assert = quote! { + const _: () = { + fn assert_pod() {} + fn assert_union_fields #impl_generics() #where_clause { + #(#field_pod_asserts)* + } + }; + }; + + // Generate the public struct + let size_expr = quote!({ ::core::mem::size_of::<#internal_ident #ty_generics>() }); + let align_expr = quote! ({::core::mem::align_of::<#internal_ident #ty_generics>()}); + let size_align_assert = quote! { + const _: () = { + let size = #size_expr; + let align = #align_expr; + assert!(size % align == 0, "size must be a multiple of align"); + }; + }; + let internal_array = quote! { + <::ostd_pod::array_helper::ArrayFactory< + { (#align_expr) }, + { (#size_expr) / (#align_expr) } + > as ::ostd_pod::array_helper::ArrayManufacture>::Array + }; + let public_struct = quote! { + #(#struct_attrs)* + pub struct #ident #impl_generics(#internal_array) #where_clause; + }; + + quote! { + mod #module_ident { + use super::*; + + #internal_union + + #public_struct + + impl #impl_generics #ident #ty_generics #where_clause { + #(#accessor_methods)* + #(#init_methods)* + } + + #pod_assert + #copy_assert + #size_align_assert + } + + #vis use #module_ident::#ident; + } +} diff --git a/ostd/libs/ostd-pod/src/array_helper.rs b/ostd/libs/ostd-pod/src/array_helper.rs new file mode 100644 index 000000000..ea24687a1 --- /dev/null +++ b/ostd/libs/ostd-pod/src/array_helper.rs @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: MPL-2.0 + +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +/// A transparent wrapper around `[u8; N]` with guaranteed 1-byte alignment. +/// +/// This type implements the zerocopy traits (`FromBytes`, `IntoBytes`, `Immutable`, `KnownLayout`) +/// making it safe to transmute to/from byte arrays. It is primarily used internally by the +/// `ArrayFactory` type system to provide aligned arrays for POD unions. +#[derive(FromBytes, IntoBytes, Immutable, KnownLayout, Clone, Copy)] +#[repr(transparent)] +pub struct U8Array([u8; N]); + +const _: () = assert!(align_of::>() == 1); + +/// A transparent wrapper around `[u16; N]` with guaranteed 2-byte alignment. +#[derive(FromBytes, IntoBytes, Immutable, KnownLayout, Clone, Copy)] +#[repr(transparent)] +pub struct U16Array([u16; N]); + +const _: () = assert!(align_of::>() == 2); + +/// A transparent wrapper around `[u32; N]` with guaranteed 4-byte alignment. +#[derive(FromBytes, IntoBytes, Immutable, KnownLayout, Clone, Copy)] +#[repr(transparent)] +pub struct U32Array([u32; N]); + +const _: () = assert!(align_of::>() == 4); + +/// A transparent wrapper around `[u64; N]` with guaranteed 8-byte alignment. +#[derive(FromBytes, IntoBytes, Immutable, KnownLayout, Clone, Copy)] +#[repr(transparent)] +pub struct U64Array([u64; N]); + +const _: () = assert!(align_of::>() == 8); + +/// A type-level factory for creating aligned arrays based on alignment requirements. +/// +/// This zero-sized type uses const generics to select the appropriate underlying array type +/// (`U8Array`, `U16Array`, `U32Array`, or `U64Array`) based on the alignment requirement `A` and +/// the number of elements `N`. +/// +/// # Type Parameters +/// +/// * `A` - The required alignment in bytes (1, 2, 4, or 8). +/// * `N` - The number of elements in the array. +/// +/// # Examples +/// +/// ```rust +/// use ostd_pod::array_helper::{ArrayFactory, ArrayManufacture}; +/// +/// // Creates a `U32Array<8>` (8 `u32` elements with 4-byte alignment) +/// type MyArray = as ArrayManufacture>::Array; +/// ``` +pub enum ArrayFactory {} + +/// Trait that associates an `ArrayFactory` with its corresponding aligned array type. +/// +/// This trait is implemented for `ArrayFactory` where `A` is 1, 2, 4, or 8, +/// mapping to `U8Array`, `U16Array`, `U32Array`, and `U64Array` respectively. +pub trait ArrayManufacture { + /// The aligned array type produced by this factory. + type Array: FromBytes + IntoBytes + Immutable; +} + +impl ArrayManufacture for ArrayFactory<1, N> { + type Array = U8Array; +} + +impl ArrayManufacture for ArrayFactory<2, N> { + type Array = U16Array; +} + +impl ArrayManufacture for ArrayFactory<4, N> { + type Array = U32Array; +} + +impl ArrayManufacture for ArrayFactory<8, N> { + type Array = U64Array; +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn u8array_alignment() { + assert_eq!(align_of::>(), 1); + assert_eq!(align_of::>(), 1); + assert_eq!(align_of::>(), 1); + } + + #[test] + fn u8array_size() { + assert_eq!(size_of::>(), 0); + assert_eq!(size_of::>(), 1); + assert_eq!(size_of::>(), 4); + assert_eq!(size_of::>(), 10); + } + + #[test] + fn u16array_alignment() { + assert_eq!(align_of::>(), 2); + assert_eq!(align_of::>(), 2); + assert_eq!(align_of::>(), 2); + } + + #[test] + fn u16array_size() { + assert_eq!(size_of::>(), 0); + assert_eq!(size_of::>(), 2); + assert_eq!(size_of::>(), 8); + assert_eq!(size_of::>(), 20); + } + + #[test] + fn u32array_alignment() { + assert_eq!(align_of::>(), 4); + assert_eq!(align_of::>(), 4); + assert_eq!(align_of::>(), 4); + } + + #[test] + fn u32array_size() { + assert_eq!(size_of::>(), 0); + assert_eq!(size_of::>(), 4); + assert_eq!(size_of::>(), 16); + assert_eq!(size_of::>(), 40); + } + + #[test] + fn u64array_alignment() { + assert_eq!(align_of::>(), 8); + assert_eq!(align_of::>(), 8); + assert_eq!(align_of::>(), 8); + } + + #[test] + fn u64array_size() { + assert_eq!(size_of::>(), 0); + assert_eq!(size_of::>(), 8); + assert_eq!(size_of::>(), 32); + assert_eq!(size_of::>(), 80); + } + + #[test] + fn array_factory_1byte_alignment() { + type Array = as ArrayManufacture>::Array; + assert_eq!(align_of::(), 1); + assert_eq!(size_of::(), 5); + } + + #[test] + fn array_factory_2byte_alignment() { + type Array = as ArrayManufacture>::Array; + assert_eq!(align_of::(), 2); + assert_eq!(size_of::(), 10); + } + + #[test] + fn array_factory_4byte_alignment() { + type Array = as ArrayManufacture>::Array; + assert_eq!(align_of::(), 4); + assert_eq!(size_of::(), 20); + } + + #[test] + fn array_factory_8byte_alignment() { + type Array = as ArrayManufacture>::Array; + assert_eq!(align_of::(), 8); + assert_eq!(size_of::(), 40); + } + + #[test] + fn zerocopy_traits() { + // Test that the types implement the required zerocopy traits + fn assert_traits() {} + + assert_traits::>(); + assert_traits::>(); + assert_traits::>(); + } +} diff --git a/ostd/libs/ostd-pod/src/lib.rs b/ostd/libs/ostd-pod/src/lib.rs new file mode 100644 index 000000000..cc3f295c6 --- /dev/null +++ b/ostd/libs/ostd-pod/src/lib.rs @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MPL-2.0 + +#![doc = include_str!("../README.md")] +#![no_std] +#![deny(unsafe_code)] + +pub use zerocopy::{FromBytes, FromZeros, Immutable, IntoBytes, KnownLayout}; + +pub mod array_helper; + +/// A trait for plain old data (POD). +/// +/// A POD type `T: Pod` can be safely converted to and from an arbitrary byte +/// sequence of length [`size_of::()`]. +/// For example, primitive types such as `u8` and `i16` are POD types. +/// +/// See the crate-level documentation for design notes and usage guidance. +/// +/// [`size_of::()`]: size_of +pub trait Pod: FromBytes + IntoBytes + KnownLayout + Immutable + Copy { + /// Creates a new instance from the given bytes. + /// + /// # Panics + /// + /// Panics if `bytes.len() != size_of::()`. + #[track_caller] + fn from_bytes(bytes: &[u8]) -> Self { + ::read_from_bytes(bytes).unwrap() + } + + /// Creates a new instance by copying the first `size_of::()` bytes from `bytes`. + /// + /// This is useful when `bytes` contains a larger buffer (e.g., a header followed by + /// payload) and you only want to interpret the prefix as `Self`. + /// + /// # Panics + /// + /// Panics if `bytes.len() < size_of::()`. + #[track_caller] + fn from_first_bytes(bytes: &[u8]) -> Self { + ::read_from_prefix(bytes).unwrap().0 + } +} + +impl Pod for T {} + +#[cfg(feature = "macros")] +pub use ostd_pod_macros::{derive, pod_union}; +#[cfg(feature = "macros")] +pub use padding_struct::padding_struct; diff --git a/ostd/libs/ostd-pod/tests/derive_test.rs b/ostd/libs/ostd-pod/tests/derive_test.rs new file mode 100644 index 000000000..90f89b52d --- /dev/null +++ b/ostd/libs/ostd-pod/tests/derive_test.rs @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MPL-2.0 + +#[macro_use] +extern crate ostd_pod; +use ostd_pod::{FromZeros, IntoBytes, Pod}; + +#[test] +fn pod_derive_simple() { + #[repr(C)] + #[derive(Pod, Debug, Clone, Copy, PartialEq)] + struct S1 { + a: u64, + b: [u8; 8], + } + + let s = S1 { + a: 42, + b: [1, 2, 3, 4, 5, 6, 7, 8], + }; + let bytes = s.as_bytes(); + assert_eq!(bytes.len(), size_of::()); + + let s2 = S1::from_bytes(bytes); + assert_eq!(s, s2); +} + +#[test] +fn pod_derive_generic() { + #[repr(C)] + #[derive(Pod, Clone, Copy, PartialEq, Debug)] + struct Item { + value: T, + } + + let item = Item { value: 5u64 }; + let bytes = item.as_bytes(); + assert_eq!(bytes.len(), size_of::>()); + + let item2 = Item::from_bytes(bytes); + assert_eq!(item, item2); +} + +#[test] +fn pod_derive_zeroed() { + #[repr(C)] + #[derive(Pod, Copy, Clone)] + struct Data { + x: u64, + y: u64, + } + + let zeroed = Data::new_zeroed(); + assert_eq!(zeroed.x, 0); + assert_eq!(zeroed.y, 0); +} diff --git a/ostd/libs/ostd-pod/tests/union_test.rs b/ostd/libs/ostd-pod/tests/union_test.rs new file mode 100644 index 000000000..f2d72c0fa --- /dev/null +++ b/ostd/libs/ostd-pod/tests/union_test.rs @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MPL-2.0 + +use ostd_pod::{FromZeros, IntoBytes, Pod, pod_union}; + +#[test] +fn union_roundtrip_from_bytes() { + #[repr(C)] + #[pod_union] + #[derive(Copy, Clone)] + union U1 { + a: u32, + b: u64, + } + + let bytes: [u8; 8] = [0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11]; + let u = U1::from_bytes(&bytes); + + assert_eq!(u.as_bytes(), &bytes); + assert_eq!(*u.b(), 0x1122_3344_5566_7788u64); +} + +#[test] +fn union_field_view_through_bytes() { + #[repr(C)] + #[pod_union] + #[derive(Copy, Clone)] + union U2 { + a: u64, + b: [u8; 8], + } + + let mut u = U2::new_zeroed(); + *u.b_mut() = [1, 2, 3, 4, 5, 6, 7, 8]; + let bytes = u.as_bytes(); + assert_eq!(bytes, &[1, 2, 3, 4, 5, 6, 7, 8]); + assert_eq!(*u.a(), 0x0807_0605_0403_0201u64); +} + +#[test] +fn union_mutable_accessor() { + #[repr(C)] + #[pod_union] + #[derive(Copy, Clone)] + union U3 { + x: u32, + y: [u8; 8], + } + + let mut u = U3::new_zeroed(); + + // Modify field through mutable accessor + *u.x_mut() = 0xAABBCCDD; + assert_eq!(*u.x(), 0xAABBCCDD); +} diff --git a/ostd/libs/padding-struct/README.md b/ostd/libs/padding-struct/README.md index d476aefc2..23590ad04 100644 --- a/ostd/libs/padding-struct/README.md +++ b/ostd/libs/padding-struct/README.md @@ -46,7 +46,7 @@ struct MyStruct { ### Integration with zerocopy -The generated structs work seamlessly with `zerocopy` for safe transmutation: +The generated structs work seamlessly with [`zerocopy`] for safe transmutation: ```rust use padding_struct::padding_struct;