Add ostd-pod crate and #[derive(pod)], pod_union macros

This commit is contained in:
jiangjianfeng 2026-01-26 08:31:35 +00:00 committed by Tate, Hongliang Tian
parent d1c9d119b3
commit c8f2cfaeae
15 changed files with 1049 additions and 6 deletions

29
Cargo.lock generated
View File

@ -361,6 +361,7 @@ dependencies = [
"inherit-methods-macro", "inherit-methods-macro",
"osdk-heap-allocator", "osdk-heap-allocator",
"ostd", "ostd",
"ostd-pod",
"typeflags-util", "typeflags-util",
] ]
@ -1350,7 +1351,7 @@ dependencies = [
"multiboot2", "multiboot2",
"num-traits", "num-traits",
"ostd-macros", "ostd-macros",
"ostd-pod", "ostd-pod 0.1.1",
"ostd-test", "ostd-test",
"riscv", "riscv",
"sbi-rt", "sbi-rt",
@ -1381,6 +1382,15 @@ dependencies = [
"ostd-pod-derive", "ostd-pod-derive",
] ]
[[package]]
name = "ostd-pod"
version = "0.1.2"
dependencies = [
"ostd-pod-macros",
"padding-struct",
"zerocopy",
]
[[package]] [[package]]
name = "ostd-pod-derive" name = "ostd-pod-derive"
version = "0.1.1" version = "0.1.1"
@ -1391,6 +1401,15 @@ dependencies = [
"syn 1.0.109", "syn 1.0.109",
] ]
[[package]]
name = "ostd-pod-macros"
version = "0.1.2"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
]
[[package]] [[package]]
name = "ostd-test" name = "ostd-test"
version = "0.17.0" version = "0.17.0"
@ -2193,18 +2212,18 @@ checksum = "2fe21bcc34ca7fe6dd56cc2cb1261ea59d6b93620215aefb5ea6032265527784"
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.8.25" version = "0.8.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" checksum = "71ddd76bcebeed25db614f82bf31a9f4222d3fbba300e6fb6c00afa26cbd4d9d"
dependencies = [ dependencies = [
"zerocopy-derive", "zerocopy-derive",
] ]
[[package]] [[package]]
name = "zerocopy-derive" name = "zerocopy-derive"
version = "0.8.25" version = "0.8.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" checksum = "d8187381b52e32220d50b255276aa16a084ec0a9017a0ca2152a1f55c539758d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

@ -19,6 +19,8 @@ members = [
"ostd/libs/linux-bzimage/boot-params", "ostd/libs/linux-bzimage/boot-params",
"ostd/libs/linux-bzimage/builder", "ostd/libs/linux-bzimage/builder",
"ostd/libs/linux-bzimage/setup", "ostd/libs/linux-bzimage/setup",
"ostd/libs/ostd-pod",
"ostd/libs/ostd-pod/macros",
"ostd/libs/ostd-macros", "ostd/libs/ostd-macros",
"ostd/libs/ostd-test", "ostd/libs/ostd-test",
"ostd/libs/padding-struct", "ostd/libs/padding-struct",
@ -142,6 +144,7 @@ serde = { version = "1.0.192", default-features = false, features = ["alloc", "d
smallvec = "1.13.2" smallvec = "1.13.2"
uart_16550 = "0.3.0" uart_16550 = "0.3.0"
volatile = "0.6.1" 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) # External dependencies only for safe crates (i.e., crates under kernel or osdk/deps directories)
# #

View File

@ -212,6 +212,8 @@ NON_OSDK_CRATES := \
ostd/libs/linux-bzimage/boot-params \ ostd/libs/linux-bzimage/boot-params \
ostd/libs/linux-bzimage/builder \ ostd/libs/linux-bzimage/builder \
ostd/libs/ostd-macros \ ostd/libs/ostd-macros \
ostd/libs/ostd-pod \
ostd/libs/ostd-pod/macros \
ostd/libs/ostd-test \ ostd/libs/ostd-test \
ostd/libs/padding-struct \ ostd/libs/padding-struct \
kernel/libs/aster-rights \ kernel/libs/aster-rights \

View File

@ -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

View File

@ -0,0 +1,175 @@
<!--
To promote a "single source of truth", the content of `README.md` is also included in `lib.rs`
as the crate-level documentation. So when writing this README, bear in mind that its content
should be recognized correctly by both a Markdown renderer and the rustdoc tool.
-->
# 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.
<!--
External links.
-->
[`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/

View File

@ -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

View File

@ -0,0 +1,53 @@
<!--
To promote a "single source of truth", the content of `README.md` is also included in `lib.rs`
as the crate-level documentation. So when writing this README, bear in mind that its content
should be recognized correctly by both a Markdown renderer and the rustdoc tool.
-->
# 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.
<!--
External links.
-->
[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

View File

@ -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(<ArrayFactory<
/// { align_of::<__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()
}

View File

@ -0,0 +1,52 @@
// SPDX-License-Identifier: MPL-2.0
use proc_macro::TokenStream;
use quote::quote;
fn push_zerocopy_derive(
derives: &mut Vec<proc_macro2::TokenTree>,
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()
}

View File

@ -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<Attribute>) -> (Vec<Attribute>, Vec<Path>) {
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<Path, Token![,]> = 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::<syn::Meta, Token![,]>::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<Path>, 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<Attribute> = 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<T: ::core::marker::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<T: ::ostd_pod::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;
}
}

View File

@ -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<const N: usize>([u8; N]);
const _: () = assert!(align_of::<U8Array<0>>() == 1);
/// A transparent wrapper around `[u16; N]` with guaranteed 2-byte alignment.
#[derive(FromBytes, IntoBytes, Immutable, KnownLayout, Clone, Copy)]
#[repr(transparent)]
pub struct U16Array<const N: usize>([u16; N]);
const _: () = assert!(align_of::<U16Array<0>>() == 2);
/// A transparent wrapper around `[u32; N]` with guaranteed 4-byte alignment.
#[derive(FromBytes, IntoBytes, Immutable, KnownLayout, Clone, Copy)]
#[repr(transparent)]
pub struct U32Array<const N: usize>([u32; N]);
const _: () = assert!(align_of::<U32Array<0>>() == 4);
/// A transparent wrapper around `[u64; N]` with guaranteed 8-byte alignment.
#[derive(FromBytes, IntoBytes, Immutable, KnownLayout, Clone, Copy)]
#[repr(transparent)]
pub struct U64Array<const N: usize>([u64; N]);
const _: () = assert!(align_of::<U64Array<0>>() == 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 = <ArrayFactory<4, 8> as ArrayManufacture>::Array;
/// ```
pub enum ArrayFactory<const A: usize, const N: usize> {}
/// Trait that associates an `ArrayFactory` with its corresponding aligned array type.
///
/// This trait is implemented for `ArrayFactory<A, N>` 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<const N: usize> ArrayManufacture for ArrayFactory<1, N> {
type Array = U8Array<N>;
}
impl<const N: usize> ArrayManufacture for ArrayFactory<2, N> {
type Array = U16Array<N>;
}
impl<const N: usize> ArrayManufacture for ArrayFactory<4, N> {
type Array = U32Array<N>;
}
impl<const N: usize> ArrayManufacture for ArrayFactory<8, N> {
type Array = U64Array<N>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn u8array_alignment() {
assert_eq!(align_of::<U8Array<0>>(), 1);
assert_eq!(align_of::<U8Array<1>>(), 1);
assert_eq!(align_of::<U8Array<10>>(), 1);
}
#[test]
fn u8array_size() {
assert_eq!(size_of::<U8Array<0>>(), 0);
assert_eq!(size_of::<U8Array<1>>(), 1);
assert_eq!(size_of::<U8Array<4>>(), 4);
assert_eq!(size_of::<U8Array<10>>(), 10);
}
#[test]
fn u16array_alignment() {
assert_eq!(align_of::<U16Array<0>>(), 2);
assert_eq!(align_of::<U16Array<1>>(), 2);
assert_eq!(align_of::<U16Array<10>>(), 2);
}
#[test]
fn u16array_size() {
assert_eq!(size_of::<U16Array<0>>(), 0);
assert_eq!(size_of::<U16Array<1>>(), 2);
assert_eq!(size_of::<U16Array<4>>(), 8);
assert_eq!(size_of::<U16Array<10>>(), 20);
}
#[test]
fn u32array_alignment() {
assert_eq!(align_of::<U32Array<0>>(), 4);
assert_eq!(align_of::<U32Array<1>>(), 4);
assert_eq!(align_of::<U32Array<10>>(), 4);
}
#[test]
fn u32array_size() {
assert_eq!(size_of::<U32Array<0>>(), 0);
assert_eq!(size_of::<U32Array<1>>(), 4);
assert_eq!(size_of::<U32Array<4>>(), 16);
assert_eq!(size_of::<U32Array<10>>(), 40);
}
#[test]
fn u64array_alignment() {
assert_eq!(align_of::<U64Array<0>>(), 8);
assert_eq!(align_of::<U64Array<1>>(), 8);
assert_eq!(align_of::<U64Array<10>>(), 8);
}
#[test]
fn u64array_size() {
assert_eq!(size_of::<U64Array<0>>(), 0);
assert_eq!(size_of::<U64Array<1>>(), 8);
assert_eq!(size_of::<U64Array<4>>(), 32);
assert_eq!(size_of::<U64Array<10>>(), 80);
}
#[test]
fn array_factory_1byte_alignment() {
type Array = <ArrayFactory<1, 5> as ArrayManufacture>::Array;
assert_eq!(align_of::<Array>(), 1);
assert_eq!(size_of::<Array>(), 5);
}
#[test]
fn array_factory_2byte_alignment() {
type Array = <ArrayFactory<2, 5> as ArrayManufacture>::Array;
assert_eq!(align_of::<Array>(), 2);
assert_eq!(size_of::<Array>(), 10);
}
#[test]
fn array_factory_4byte_alignment() {
type Array = <ArrayFactory<4, 5> as ArrayManufacture>::Array;
assert_eq!(align_of::<Array>(), 4);
assert_eq!(size_of::<Array>(), 20);
}
#[test]
fn array_factory_8byte_alignment() {
type Array = <ArrayFactory<8, 5> as ArrayManufacture>::Array;
assert_eq!(align_of::<Array>(), 8);
assert_eq!(size_of::<Array>(), 40);
}
#[test]
fn zerocopy_traits() {
// Test that the types implement the required zerocopy traits
fn assert_traits<T: FromBytes + IntoBytes + Immutable + KnownLayout>() {}
assert_traits::<U16Array<4>>();
assert_traits::<U32Array<4>>();
assert_traits::<U64Array<4>>();
}
}

View File

@ -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::<T>()`].
/// 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::<T>()`]: 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::<Self>()`.
#[track_caller]
fn from_bytes(bytes: &[u8]) -> Self {
<Self as FromBytes>::read_from_bytes(bytes).unwrap()
}
/// Creates a new instance by copying the first `size_of::<Self>()` 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::<Self>()`.
#[track_caller]
fn from_first_bytes(bytes: &[u8]) -> Self {
<Self as FromBytes>::read_from_prefix(bytes).unwrap().0
}
}
impl<T: FromBytes + IntoBytes + KnownLayout + Immutable + Copy> Pod for T {}
#[cfg(feature = "macros")]
pub use ostd_pod_macros::{derive, pod_union};
#[cfg(feature = "macros")]
pub use padding_struct::padding_struct;

View File

@ -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::<S1>());
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<T: Pod> {
value: T,
}
let item = Item { value: 5u64 };
let bytes = item.as_bytes();
assert_eq!(bytes.len(), size_of::<Item<u64>>());
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);
}

View File

@ -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);
}

View File

@ -46,7 +46,7 @@ struct MyStruct {
### Integration with zerocopy ### Integration with zerocopy
The generated structs work seamlessly with `zerocopy` for safe transmutation: The generated structs work seamlessly with [`zerocopy`] for safe transmutation:
```rust ```rust
use padding_struct::padding_struct; use padding_struct::padding_struct;