Add ostd-pod crate and #[derive(pod)], pod_union macros
This commit is contained in:
parent
d1c9d119b3
commit
c8f2cfaeae
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
#
|
#
|
||||||
|
|
|
||||||
2
Makefile
2
Makefile
|
|
@ -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 \
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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/
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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>>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue