From 074b72747e9d072d334e132cfffabc92cc59963f Mon Sep 17 00:00:00 2001 From: Lars Wrenger Date: Fri, 3 Feb 2023 14:16:51 +0100 Subject: [PATCH 1/9] :contruction: Bitcopy --- Cargo.lock | 10 + Cargo.toml | 4 +- derive/Cargo.toml | 19 ++ derive/src/lib.rs | 644 +++++++++++++++++++++++++++++++++++ src/lib.rs | 851 ++++++++++++++-------------------------------- tests/test.rs | 121 ------- 6 files changed, 924 insertions(+), 725 deletions(-) create mode 100644 derive/Cargo.toml create mode 100644 derive/src/lib.rs delete mode 100644 tests/test.rs diff --git a/Cargo.lock b/Cargo.lock index 14cd509..2652889 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5,6 +5,16 @@ version = 3 [[package]] name = "bitfield-struct" version = "0.3.2" +dependencies = [ + "bitfield-struct-derive", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bitfield-struct-derive" +version = "0.3.2" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 69e42c3..17cd255 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,10 +10,8 @@ repository = "https://github.com/wrenger/bitfield-struct-rs.git" readme = "README.md" license = "MIT" -[lib] -proc-macro = true - [dependencies] +bitfield-struct-derive = { version = "0.3.2", path = "derive" } quote = "1.0" syn = { version = "1.0", features = ["full"] } proc-macro2 = "1.0" diff --git a/derive/Cargo.toml b/derive/Cargo.toml new file mode 100644 index 0000000..b587114 --- /dev/null +++ b/derive/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "bitfield-struct-derive" +version = "0.3.2" +edition = "2021" +authors = ["Lars Wrenger "] +description = "Procedural macro for bitfields." +keywords = ["bitfield", "bits", "bitfields", "proc-macro"] +categories = ["data-structures", "no-std"] +repository = "https://github.com/wrenger/bitfield-struct-rs.git" +readme = "README.md" +license = "MIT" + +[lib] +proc-macro = true + +[dependencies] +quote = "1.0" +syn = { version = "1.0", features = ["full"] } +proc-macro2 = "1.0" diff --git a/derive/src/lib.rs b/derive/src/lib.rs new file mode 100644 index 0000000..3e9b085 --- /dev/null +++ b/derive/src/lib.rs @@ -0,0 +1,644 @@ +//! # Bitfield Struct +//! +//! Procedural macro for bitfields that allows specifying bitfields as structs. +//! As this library provides a procedural-macro it has no runtime dependencies and works for `no-std`. +//! +//! - Supports bool flags, raw integers, and every custom type convertible into integers (structs/enums) +//! - Ideal for driver/OS/embedded development (defining HW registers/structures) +//! - Generates minimalistic, pure, safe rust functions +//! - Compile-time checks for type and field sizes +//! - Rust-analyzer friendly (carries over documentation to accessor functions) +//! - Exports field offsets and sizes as constants (useful for const asserts) +//! - Generation of `fmt::Debug` +//! +//! ## Basics +//! +//! Let's begin with a simple example.
+//! Suppose we want to store multiple data inside a single Byte, as shown below: +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//!
76543310
PLevelSKind
+//! +//! This crate is able to generate a nice wrapper type that makes it easy to do this: +//! +//! ``` +//! # use bitfield_struct::bitfield; +//! /// Define your type like this with the bitfield attribute +//! #[bitfield(u8)] +//! struct MyByte { +//! /// The first field occupies the least significant bits +//! #[bits(4)] +//! kind: usize, +//! /// Booleans are 1 bit large +//! system: bool, +//! /// The bits attribute specifies the bit size of this field +//! #[bits(2)] +//! level: usize, +//! /// The last field spans over the most significant bits +//! present: bool +//! } +//! // The macro creates three accessor functions for each field: +//! // , with_ and set_ +//! let my_byte = MyByte::new() +//! .with_kind(15) +//! .with_system(false) +//! .with_level(3) +//! .with_present(true); +//! +//! assert!(my_byte.present()); +//! ``` +//! +//! ## Features +//! +//! Additionally, this crate has a few useful features, which are shown here in more detail. +//! +//! The example below shows how attributes are carried over and how signed integers, padding, and custom types are handled. +//! +//! ``` +//! # use bitfield_struct::bitfield; +//! /// A test bitfield with documentation +//! #[bitfield(u64)] +//! #[derive(PartialEq, Eq)] // <- Attributes after `bitfield` are carried over +//! struct MyBitfield { +//! /// defaults to 16 bits for u16 +//! int: u16, +//! /// interpreted as 1 bit flag +//! flag: bool, +//! /// custom bit size +//! #[bits(1)] +//! tiny: u8, +//! /// sign extend for signed integers +//! #[bits(13)] +//! negative: i16, +//! /// supports any type that implements `From` and `Into` +//! #[bits(16)] +//! custom: CustomEnum, +//! /// public field -> public accessor functions +//! #[bits(12)] +//! pub public: usize, +//! /// padding +//! #[bits(5)] +//! _p: u8, +//! /// zero-sized members are ignored +//! #[bits(0)] +//! _completely_ignored: String, +//! } +//! +//! /// A custom enum +//! #[derive(Debug, PartialEq, Eq)] +//! #[repr(u64)] +//! enum CustomEnum { +//! A = 0, +//! B = 1, +//! C = 2, +//! } +//! // implement `From` and `Into` for `CustomEnum`! +//! # impl From for CustomEnum { +//! # fn from(value: u64) -> Self { +//! # match value { +//! # 0 => Self::A, +//! # 1 => Self::B, +//! # _ => Self::C, +//! # } +//! # } +//! # } +//! # impl From for u64 { +//! # fn from(value: CustomEnum) -> Self { +//! # value as _ +//! # } +//! # } +//! +//! // Usage: +//! let mut val = MyBitfield::new() +//! .with_int(3 << 15) +//! .with_flag(true) +//! .with_tiny(1) +//! .with_negative(-3) +//! .with_custom(CustomEnum::B) +//! .with_public(2); +//! +//! println!("{val:?}"); +//! let raw: u64 = val.into(); +//! println!("{raw:b}"); +//! +//! assert_eq!(val.int(), 3 << 15); +//! assert_eq!(val.flag(), true); +//! assert_eq!(val.negative(), -3); +//! assert_eq!(val.tiny(), 1); +//! assert_eq!(val.custom(), CustomEnum::B); +//! assert_eq!(val.public(), 2); +//! +//! // const members +//! assert_eq!(MyBitfield::FLAG_BITS, 1); +//! assert_eq!(MyBitfield::FLAG_OFFSET, 16); +//! +//! val.set_negative(1); +//! assert_eq!(val.negative(), 1); +//! ``` +//! +//! The macro generates three accessor functions for each field. +//! Each accessor also inherits the documentation of its field. +//! +//! The signatures for `int` are: +//! +//! ```ignore +//! // generated struct +//! struct MyBitfield(u64); +//! impl MyBitfield { +//! const fn new() -> Self { Self(0) } +//! +//! const INT_BITS: usize = 16; +//! const INT_OFFSET: usize = 0; +//! +//! const fn with_int(self, value: u16) -> Self { /* ... */ } +//! const fn int(&self) -> u16 { /* ... */ } +//! fn set_int(&mut self, value: u16) { /* ... */ } +//! +//! // other field ... +//! } +//! // generated trait implementations +//! impl From for MyBitfield { /* ... */ } +//! impl From for u64 { /* ... */ } +//! impl Debug for MyBitfield { /* ... */ } +//! ``` +//! +//! > Hint: You can use the rust-analyzer "Expand macro recursively" action to view the generated code. +//! +//! ## `fmt::Debug` +//! +//! This macro automatically creates a suitable `fmt::Debug` implementation +//! similar to the ones created for normal structs by `#[derive(Debug)]`. +//! You can disable it with the extra debug argument. +//! +//! ``` +//! # use std::fmt; +//! # use bitfield_struct::bitfield; +//! +//! #[bitfield(u64, debug = false)] +//! struct CustomDebug { +//! data: u64 +//! } +//! +//! impl fmt::Debug for CustomDebug { +//! fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +//! write!(f, "0x{:x}", self.data()) +//! } +//! } +//! +//! let val = CustomDebug::new().with_data(123); +//! println!("{val:?}") +//! ``` +//! + +use proc_macro as pc; +use proc_macro2::{Ident, Span, TokenStream}; +use quote::{format_ident, quote, ToTokens}; +use std::stringify; +use syn::parse::{Parse, ParseStream}; +use syn::spanned::Spanned; +use syn::Token; + +/// Creates a bitfield for this struct. +/// +/// The arguments first, have to begin with the underlying type of the bitfield: +/// For example: `#[bitfield(u64)]`. +/// +/// It can contain an extra `debug` argument for disabling the `Debug` trait +/// generation (`#[bitfield(u64, debug = false)]`). +#[proc_macro_attribute] +pub fn bitfield(args: pc::TokenStream, input: pc::TokenStream) -> pc::TokenStream { + match bitfield_inner(args.into(), input.into()) { + Ok(result) => result.into(), + Err(e) => e.into_compile_error().into(), + } +} + +fn bitfield_inner(args: TokenStream, input: TokenStream) -> syn::Result { + let input = syn::parse2::(input)?; + let Params { ty, bits, debug } = + syn::parse2::(args).map_err(|e| unsupported_param(e, input.span()))?; + + let span = input.fields.span(); + let name = input.ident; + let name_str = name.to_string(); + let vis = input.vis; + let attrs: TokenStream = input.attrs.iter().map(ToTokens::to_token_stream).collect(); + + let syn::Fields::Named(fields) = input.fields else { + return Err(syn::Error::new(span, "only named fields are supported")); + }; + + let mut offset = 0; + let mut members = Vec::with_capacity(fields.named.len()); + for field in fields.named { + let f = Member::new(ty.clone(), field, offset)?; + offset += f.bits; + members.push(f); + } + + if offset < bits { + return Err(syn::Error::new( + span, + format!( + "The bitfiled size ({bits} bits) has to be equal to the sum of its members ({offset} bits)!. \ + You might have to add padding (a {} bits large member prefixed with \"_\").", + bits - offset + ), + )); + } + if offset > bits { + return Err(syn::Error::new( + span, + format!( + "The size of the members ({offset} bits) is larger than the type ({bits} bits)!." + ), + )); + } + + let debug_impl = if debug { + let debug_fields = members.iter().map(|m| m.debug()); + quote! { + impl core::fmt::Debug for #name { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct(#name_str) + #( #debug_fields )* + .finish() + } + } + } + } else { + Default::default() + }; + + // The size of isize and usize is architecture dependent and not known for proc_macros, + // thus we have to check it with const asserts. + let const_asserts = members.iter().filter_map(|m| { + if m.class == TypeClass::SizeInt { + let bits = m.bits; + let msg = format!("overflowing field type of '{}'", m.ident); + Some(quote!( + const _: () = assert!(#bits <= 8 * std::mem::size_of::(), #msg); + )) + } else { + None + } + }); + + Ok(quote! { + #attrs + #[derive(Copy, Clone)] + #[repr(transparent)] + #vis struct #name(#ty); + + impl #name { + #vis const fn new() -> Self { + Self(0) + } + + #( #members )* + } + + impl From<#ty> for #name { + fn from(v: #ty) -> Self { + Self(v) + } + } + impl From<#name> for #ty { + fn from(v: #name) -> #ty { + v.0 + } + } + + #( #const_asserts )* + + #debug_impl + }) +} + +/// Distinguish between different types for code generation. +/// +/// We need this to make accessor functions for bool and ints const. +/// As soon as we have const conversion traits, we can simply switch to `TryFrom` and don't have to generate different code. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +enum TypeClass { + /// Booleans with 1 bit size + Bool, + /// Ints with fixes sizes: u8, u64, ... + Int, + /// Ints with architecture dependend sizes: usize, isize + SizeInt, + /// Custom types + Other, +} + +struct Member { + base_ty: syn::Type, + attrs: Vec, + ty: syn::Type, + class: TypeClass, + bits: usize, + ident: syn::Ident, + vis: syn::Visibility, + offset: usize, +} + +impl Member { + fn new(base_ty: syn::Type, f: syn::Field, offset: usize) -> syn::Result { + let span = f.span(); + + let syn::Field { + mut attrs, + vis, + ident, + ty, + .. + } = f; + + let ident = ident.ok_or_else(|| syn::Error::new(span, "Not supported"))?; + + let (class, bits) = bits(&attrs, &ty)?; + // remove our attribute + attrs.retain(|a| !a.path.is_ident("bits")); + + Ok(Self { + base_ty, + attrs, + ty, + class, + bits, + ident, + vis, + offset, + }) + } + + fn debug(&self) -> TokenStream { + let ident_str = self.ident.to_string(); + if self.bits > 0 && !ident_str.starts_with('_') { + let ident = &self.ident; + quote!(.field(#ident_str, &self.#ident())) + } else { + Default::default() + } + } +} + +impl ToTokens for Member { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + base_ty, + attrs, + ty, + class, + bits, + ident, + vis, + offset, + } = self; + let ident_str = ident.to_string(); + + // Skip zero sized and padding members + if self.bits == 0 || ident_str.starts_with('_') { + return Default::default(); + } + + let with_ident = format_ident!("with_{ident}"); + let set_ident = format_ident!("set_{ident}"); + let bits_ident = format_ident!("{}_BITS", ident_str.to_uppercase()); + let offset_ident = format_ident!("{}_OFFSET", ident_str.to_uppercase()); + + let location = format!("\n\nBits: {offset}..{}", offset + bits); + + let doc: TokenStream = attrs + .iter() + .filter(|a| !a.path.is_ident("bits")) + .map(ToTokens::to_token_stream) + .collect(); + + let general = quote! { + const #bits_ident: usize = #bits; + const #offset_ident: usize = #offset; + + #doc + #[doc = #location] + #vis fn #set_ident(&mut self, value: #ty) { + *self = self.#with_ident(value); + } + }; + + let mask: u128 = !0 >> (u128::BITS as usize - bits); + let mask = syn::LitInt::new(&format!("0x{mask:x}"), Span::mixed_site()); + + let code = match class { + TypeClass::Bool => quote! { + #general + + #doc + #[doc = #location] + #vis const fn #with_ident(self, value: #ty) -> Self { + Self(self.0 & !(1 << #offset) | (value as #base_ty) << #offset) + } + #doc + #[doc = #location] + #vis const fn #ident(&self) -> #ty { + ((self.0 >> #offset) & 1) != 0 + } + }, + TypeClass::Int | TypeClass::SizeInt => quote! { + #general + + #doc + #[doc = #location] + #vis const fn #with_ident(self, value: #ty) -> Self { + debug_assert!(value <= #mask); + Self(self.0 & !(#mask << #offset) | (value as #base_ty & #mask) << #offset) + } + #doc + #[doc = #location] + #vis const fn #ident(&self) -> #ty { + let shift = #ty::BITS as usize - #bits; + (((self.0 >> #offset) as #ty) << shift) >> shift + } + }, + TypeClass::Other => quote! { + #general + + #doc + #[doc = #location] + #vis fn #with_ident(self, value: #ty) -> Self { + let value: #base_ty = value.into(); + debug_assert!(value <= #mask); + Self(self.0 & !(#mask << #offset) | (value & #mask) << #offset) + } + #doc + #[doc = #location] + #vis fn #ident(&self) -> #ty { + let shift = #base_ty::BITS as usize - #bits; + (((self.0 >> #offset) << shift) >> shift).into() + } + }, + }; + tokens.extend(code); + } +} + +/// Parses the `bits` attribute that allows specifying a custom number of bits. +fn bits(attrs: &[syn::Attribute], ty: &syn::Type) -> syn::Result<(TypeClass, usize)> { + fn malformed(mut e: syn::Error, attr: &syn::Attribute) -> syn::Error { + e.combine(syn::Error::new(attr.span(), "malformed #[bits] attribute")); + e + } + + for attr in attrs { + match attr { + syn::Attribute { + style: syn::AttrStyle::Outer, + path, + tokens, + .. + } if path.is_ident("bits") => { + let bits = attr + .parse_args::() + .map_err(|e| malformed(e, attr))? + .base10_parse() + .map_err(|e| malformed(e, attr))?; + + return if bits == 0 { + Ok((TypeClass::Other, 0)) + } else if let Ok((class, size)) = type_bits(ty) { + if bits <= size { + Ok((class, bits)) + } else { + Err(syn::Error::new(tokens.span(), "overflowing field type")) + } + } else if matches!(ty, syn::Type::Path(syn::TypePath{ path, .. }) + if path.is_ident("usize") || path.is_ident("isize")) + { + // isize and usize are supported but types size is not known at this point! + // Meaning that they must have a bits attribute explicitly defining their size + Ok((TypeClass::SizeInt, bits)) + } else { + Ok((TypeClass::Other, bits)) + }; + } + _ => {} + } + } + + if let syn::Type::Path(syn::TypePath { path, .. }) = ty { + if path.is_ident("usize") || path.is_ident("isize") { + return Err(syn::Error::new( + ty.span(), + "isize and usize fields require the #[bits($1)] attribute", + )); + } + } + + // Fallback to type size + type_bits(ty) +} + +/// Returns the number of bits for a given type +fn type_bits(ty: &syn::Type) -> syn::Result<(TypeClass, usize)> { + let syn::Type::Path(syn::TypePath{ path, .. }) = ty else { + return Err(syn::Error::new(ty.span(), "unsupported type")) + }; + let Some(ident) = path.get_ident() else { + return Err(syn::Error::new(ty.span(), "unsupported type")) + }; + if ident == "bool" { + return Ok((TypeClass::Bool, 1)); + } + macro_rules! integer { + ($ident:ident => $($ty:ident),*) => { + match ident { + $(_ if ident == stringify!($ty) => Ok((TypeClass::Int, $ty::BITS as _)),)* + _ => Err(syn::Error::new(ty.span(), "unsupported type")) + } + }; + } + integer!(ident => u8, i8, u16, i16, u32, i32, u64, i64, u128, i128) +} + +struct Params { + ty: syn::Type, + bits: usize, + debug: bool, +} + +impl Parse for Params { + fn parse(input: ParseStream) -> syn::Result { + let Ok(ty) = syn::Type::parse(input) else { + return Err(syn::Error::new(input.span(), "unknown type")); + }; + let (class, bits) = type_bits(&ty)?; + if class != TypeClass::Int { + return Err(syn::Error::new(input.span(), "unsupported type")); + } + + // try parse additional debug arg + let debug = if ::parse(input).is_ok() { + let ident = Ident::parse(input)?; + + if ident != "debug" { + return Err(syn::Error::new(ident.span(), "unknown argument")); + } + ::parse(input)?; + + syn::LitBool::parse(input)?.value + } else { + true + }; + + Ok(Params { bits, ty, debug }) + } +} + +fn unsupported_param(mut e: syn::Error, arg: T) -> syn::Error +where + T: syn::spanned::Spanned, +{ + e.combine(syn::Error::new( + arg.span(), + "unsupported #[bitfield] argument", + )); + e +} + +#[cfg(test)] +mod test { + use quote::quote; + + use crate::Params; + + #[test] + fn parse_args() { + let args = quote! { + u64 + }; + let params = syn::parse2::(args).unwrap(); + assert!(params.bits == u64::BITS as usize && params.debug == true); + + let args = quote! { + u32, debug = false + }; + let params = syn::parse2::(args).unwrap(); + assert!(params.bits == u32::BITS as usize && params.debug == false); + } +} diff --git a/src/lib.rs b/src/lib.rs index 3e9b085..f6f4a03 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,644 +1,293 @@ -//! # Bitfield Struct -//! -//! Procedural macro for bitfields that allows specifying bitfields as structs. -//! As this library provides a procedural-macro it has no runtime dependencies and works for `no-std`. -//! -//! - Supports bool flags, raw integers, and every custom type convertible into integers (structs/enums) -//! - Ideal for driver/OS/embedded development (defining HW registers/structures) -//! - Generates minimalistic, pure, safe rust functions -//! - Compile-time checks for type and field sizes -//! - Rust-analyzer friendly (carries over documentation to accessor functions) -//! - Exports field offsets and sizes as constants (useful for const asserts) -//! - Generation of `fmt::Debug` -//! -//! ## Basics -//! -//! Let's begin with a simple example.
-//! Suppose we want to store multiple data inside a single Byte, as shown below: -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//!
76543310
PLevelSKind
-//! -//! This crate is able to generate a nice wrapper type that makes it easy to do this: -//! -//! ``` -//! # use bitfield_struct::bitfield; -//! /// Define your type like this with the bitfield attribute -//! #[bitfield(u8)] -//! struct MyByte { -//! /// The first field occupies the least significant bits -//! #[bits(4)] -//! kind: usize, -//! /// Booleans are 1 bit large -//! system: bool, -//! /// The bits attribute specifies the bit size of this field -//! #[bits(2)] -//! level: usize, -//! /// The last field spans over the most significant bits -//! present: bool -//! } -//! // The macro creates three accessor functions for each field: -//! // , with_ and set_ -//! let my_byte = MyByte::new() -//! .with_kind(15) -//! .with_system(false) -//! .with_level(3) -//! .with_present(true); -//! -//! assert!(my_byte.present()); -//! ``` -//! -//! ## Features -//! -//! Additionally, this crate has a few useful features, which are shown here in more detail. -//! -//! The example below shows how attributes are carried over and how signed integers, padding, and custom types are handled. -//! -//! ``` -//! # use bitfield_struct::bitfield; -//! /// A test bitfield with documentation -//! #[bitfield(u64)] -//! #[derive(PartialEq, Eq)] // <- Attributes after `bitfield` are carried over -//! struct MyBitfield { -//! /// defaults to 16 bits for u16 -//! int: u16, -//! /// interpreted as 1 bit flag -//! flag: bool, -//! /// custom bit size -//! #[bits(1)] -//! tiny: u8, -//! /// sign extend for signed integers -//! #[bits(13)] -//! negative: i16, -//! /// supports any type that implements `From` and `Into` -//! #[bits(16)] -//! custom: CustomEnum, -//! /// public field -> public accessor functions -//! #[bits(12)] -//! pub public: usize, -//! /// padding -//! #[bits(5)] -//! _p: u8, -//! /// zero-sized members are ignored -//! #[bits(0)] -//! _completely_ignored: String, -//! } -//! -//! /// A custom enum -//! #[derive(Debug, PartialEq, Eq)] -//! #[repr(u64)] -//! enum CustomEnum { -//! A = 0, -//! B = 1, -//! C = 2, -//! } -//! // implement `From` and `Into` for `CustomEnum`! -//! # impl From for CustomEnum { -//! # fn from(value: u64) -> Self { -//! # match value { -//! # 0 => Self::A, -//! # 1 => Self::B, -//! # _ => Self::C, -//! # } -//! # } -//! # } -//! # impl From for u64 { -//! # fn from(value: CustomEnum) -> Self { -//! # value as _ -//! # } -//! # } -//! -//! // Usage: -//! let mut val = MyBitfield::new() -//! .with_int(3 << 15) -//! .with_flag(true) -//! .with_tiny(1) -//! .with_negative(-3) -//! .with_custom(CustomEnum::B) -//! .with_public(2); -//! -//! println!("{val:?}"); -//! let raw: u64 = val.into(); -//! println!("{raw:b}"); -//! -//! assert_eq!(val.int(), 3 << 15); -//! assert_eq!(val.flag(), true); -//! assert_eq!(val.negative(), -3); -//! assert_eq!(val.tiny(), 1); -//! assert_eq!(val.custom(), CustomEnum::B); -//! assert_eq!(val.public(), 2); -//! -//! // const members -//! assert_eq!(MyBitfield::FLAG_BITS, 1); -//! assert_eq!(MyBitfield::FLAG_OFFSET, 16); -//! -//! val.set_negative(1); -//! assert_eq!(val.negative(), 1); -//! ``` -//! -//! The macro generates three accessor functions for each field. -//! Each accessor also inherits the documentation of its field. -//! -//! The signatures for `int` are: -//! -//! ```ignore -//! // generated struct -//! struct MyBitfield(u64); -//! impl MyBitfield { -//! const fn new() -> Self { Self(0) } -//! -//! const INT_BITS: usize = 16; -//! const INT_OFFSET: usize = 0; -//! -//! const fn with_int(self, value: u16) -> Self { /* ... */ } -//! const fn int(&self) -> u16 { /* ... */ } -//! fn set_int(&mut self, value: u16) { /* ... */ } -//! -//! // other field ... -//! } -//! // generated trait implementations -//! impl From for MyBitfield { /* ... */ } -//! impl From for u64 { /* ... */ } -//! impl Debug for MyBitfield { /* ... */ } -//! ``` -//! -//! > Hint: You can use the rust-analyzer "Expand macro recursively" action to view the generated code. -//! -//! ## `fmt::Debug` -//! -//! This macro automatically creates a suitable `fmt::Debug` implementation -//! similar to the ones created for normal structs by `#[derive(Debug)]`. -//! You can disable it with the extra debug argument. -//! -//! ``` -//! # use std::fmt; -//! # use bitfield_struct::bitfield; -//! -//! #[bitfield(u64, debug = false)] -//! struct CustomDebug { -//! data: u64 -//! } -//! -//! impl fmt::Debug for CustomDebug { -//! fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -//! write!(f, "0x{:x}", self.data()) -//! } -//! } -//! -//! let val = CustomDebug::new().with_data(123); -//! println!("{val:?}") -//! ``` -//! - -use proc_macro as pc; -use proc_macro2::{Ident, Span, TokenStream}; -use quote::{format_ident, quote, ToTokens}; -use std::stringify; -use syn::parse::{Parse, ParseStream}; -use syn::spanned::Spanned; -use syn::Token; - -/// Creates a bitfield for this struct. +pub use bitfield_struct_derive::bitfield; + +/// The heart of the bitfield macro /// -/// The arguments first, have to begin with the underlying type of the bitfield: -/// For example: `#[bitfield(u64)]`. +/// General idea. +/// - Copy prefix, suffix bits. +/// - Copy aligned u8 +/// - Copy aligned u16 for the inner array +/// - Copy aligned u32 for the inner inner array +/// - Copy aligned u64 for the inner inner inner array /// -/// It can contain an extra `debug` argument for disabling the `Debug` trait -/// generation (`#[bitfield(u64, debug = false)]`). -#[proc_macro_attribute] -pub fn bitfield(args: pc::TokenStream, input: pc::TokenStream) -> pc::TokenStream { - match bitfield_inner(args.into(), input.into()) { - Ok(result) => result.into(), - Err(e) => e.into_compile_error().into(), +/// FIXME: Use mutable reference as soon as `const_mut_refs` is stable +#[inline] +pub fn bit_copy(dst: &mut [u8], dst_off: usize, src: &[u8], src_off: usize, len: usize) { + println!("copy {dst:x?}, {dst_off}, {src:x?}, {src_off}, {len}"); + debug_assert!(len > 0); + debug_assert!(dst.len() * 8 - dst_off >= len); + debug_assert!(src.len() * 8 - src_off >= len); + + let mut len = len; + let mut dst = &mut dst[dst_off / 8..]; + let mut src = &src[src_off / 8..]; + let dst_off = dst_off % 8; + let mut src_off = src_off % 8; + + if len < (8 - dst_off) { + single_byte(&mut dst[0], dst_off, src, src_off, len); + return; } -} -fn bitfield_inner(args: TokenStream, input: TokenStream) -> syn::Result { - let input = syn::parse2::(input)?; - let Params { ty, bits, debug } = - syn::parse2::(args).map_err(|e| unsupported_param(e, input.span()))?; - - let span = input.fields.span(); - let name = input.ident; - let name_str = name.to_string(); - let vis = input.vis; - let attrs: TokenStream = input.attrs.iter().map(ToTokens::to_token_stream).collect(); - - let syn::Fields::Named(fields) = input.fields else { - return Err(syn::Error::new(span, "only named fields are supported")); - }; - - let mut offset = 0; - let mut members = Vec::with_capacity(fields.named.len()); - for field in fields.named { - let f = Member::new(ty.clone(), field, offset)?; - offset += f.bits; - members.push(f); + // copy prefix + if dst_off > 0 { + let prefix = 8 - dst_off; + println!("copy prefix {prefix}"); + let mask = u8::MAX << dst_off; + dst[0] &= !mask; + dst[0] |= (src[0] >> src_off) << dst_off; + + // exceeding a single byte of the src buffer + src_off += prefix; + if let Some(reminder) = src_off.checked_sub(8) { + src = &src[1..]; + if reminder > 0 { + dst[0] |= src[0] << (dst_off + reminder) + } + src_off = reminder + } + dst = &mut dst[1..]; + len -= prefix; } - if offset < bits { - return Err(syn::Error::new( - span, - format!( - "The bitfiled size ({bits} bits) has to be equal to the sum of its members ({offset} bits)!. \ - You might have to add padding (a {} bits large member prefixed with \"_\").", - bits - offset - ), - )); - } - if offset > bits { - return Err(syn::Error::new( - span, - format!( - "The size of the members ({offset} bits) is larger than the type ({bits} bits)!." - ), - )); + if src_off == 0 { + copy_aligned(dst, src, len); + } else { + copy_dst_aligned(dst, src, src_off, len); } +} - let debug_impl = if debug { - let debug_fields = members.iter().map(|m| m.debug()); - quote! { - impl core::fmt::Debug for #name { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct(#name_str) - #( #debug_fields )* - .finish() - } - } - } - } else { - Default::default() - }; - - // The size of isize and usize is architecture dependent and not known for proc_macros, - // thus we have to check it with const asserts. - let const_asserts = members.iter().filter_map(|m| { - if m.class == TypeClass::SizeInt { - let bits = m.bits; - let msg = format!("overflowing field type of '{}'", m.ident); - Some(quote!( - const _: () = assert!(#bits <= 8 * std::mem::size_of::(), #msg); - )) - } else { - None - } - }); +fn single_byte(dst: &mut u8, dst_off: usize, src: &[u8], src_off: usize, len: usize) { + println!("copy small"); - Ok(quote! { - #attrs - #[derive(Copy, Clone)] - #[repr(transparent)] - #vis struct #name(#ty); + let mask = (u8::MAX >> (8 - len)) << dst_off; + *dst &= !mask; + *dst |= ((src[0] >> src_off) << dst_off) & mask; - impl #name { - #vis const fn new() -> Self { - Self(0) - } + // exceeding a single byte of the src buffer + if len + src_off > 8 { + *dst |= (src[1] << (8 - src_off + dst_off)) & mask; + } +} - #( #members )* - } +#[inline] +fn copy_dst_aligned(dst: &mut [u8], src: &[u8], src_off: usize, len: usize) { + println!("dst_aligned {dst:?}, {src:?}, {src_off}, {len}"); + debug_assert!(0 < src_off && src_off < 8); + debug_assert!(dst.len() * 8 >= len); + debug_assert!(src.len() * 8 - src_off >= len); // has to be one larger - impl From<#ty> for #name { - fn from(v: #ty) -> Self { - Self(v) - } - } - impl From<#name> for #ty { - fn from(v: #name) -> #ty { - v.0 - } - } - - #( #const_asserts )* + // copy middle + for i in 0..len / 8 { + dst[i] = (src[i] >> src_off) | (src[i + 1] << (8 - src_off)); + } + println!("middle {dst:x?}"); - #debug_impl - }) -} + // suffix + let suffix = len % 8; + if suffix > 0 { + println!("copy suffix {suffix}"); + let last = len / 8; + let mask = u8::MAX >> (8 - suffix); -/// Distinguish between different types for code generation. -/// -/// We need this to make accessor functions for bool and ints const. -/// As soon as we have const conversion traits, we can simply switch to `TryFrom` and don't have to generate different code. -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -enum TypeClass { - /// Booleans with 1 bit size - Bool, - /// Ints with fixes sizes: u8, u64, ... - Int, - /// Ints with architecture dependend sizes: usize, isize - SizeInt, - /// Custom types - Other, -} + dst[last] &= !mask; + dst[last] |= src[last] >> src_off; -struct Member { - base_ty: syn::Type, - attrs: Vec, - ty: syn::Type, - class: TypeClass, - bits: usize, - ident: syn::Ident, - vis: syn::Visibility, - offset: usize, + // exceeding a single byte of the src buffer + if suffix + src_off > 8 { + dst[last] |= (src[last + 1] << (8 - src_off)) & mask; + } + } } - -impl Member { - fn new(base_ty: syn::Type, f: syn::Field, offset: usize) -> syn::Result { - let span = f.span(); - - let syn::Field { - mut attrs, - vis, - ident, - ty, - .. - } = f; - - let ident = ident.ok_or_else(|| syn::Error::new(span, "Not supported"))?; - - let (class, bits) = bits(&attrs, &ty)?; - // remove our attribute - attrs.retain(|a| !a.path.is_ident("bits")); - - Ok(Self { - base_ty, - attrs, - ty, - class, - bits, - ident, - vis, - offset, - }) +#[inline] +fn copy_aligned(dst: &mut [u8], src: &[u8], len: usize) { + println!("aligned {dst:?}, {src:?}, {len}"); + debug_assert!(dst.len() * 8 >= len); + debug_assert!(src.len() * 8 >= len); + + // copy middle + for i in 0..len / 8 { + dst[i] = src[i]; } - - fn debug(&self) -> TokenStream { - let ident_str = self.ident.to_string(); - if self.bits > 0 && !ident_str.starts_with('_') { - let ident = &self.ident; - quote!(.field(#ident_str, &self.#ident())) - } else { - Default::default() - } + // suffix + let suffix = len % 8; + if suffix > 0 { + println!("copy suffix {suffix}"); + + let last = len / 8; + let mask = u8::MAX >> suffix; + dst[last] &= mask; + dst[last] |= src[last]; } } -impl ToTokens for Member { - fn to_tokens(&self, tokens: &mut TokenStream) { - let Self { - base_ty, - attrs, - ty, - class, - bits, - ident, - vis, - offset, - } = self; - let ident_str = ident.to_string(); - - // Skip zero sized and padding members - if self.bits == 0 || ident_str.starts_with('_') { - return Default::default(); - } - - let with_ident = format_ident!("with_{ident}"); - let set_ident = format_ident!("set_{ident}"); - let bits_ident = format_ident!("{}_BITS", ident_str.to_uppercase()); - let offset_ident = format_ident!("{}_OFFSET", ident_str.to_uppercase()); - - let location = format!("\n\nBits: {offset}..{}", offset + bits); - - let doc: TokenStream = attrs - .iter() - .filter(|a| !a.path.is_ident("bits")) - .map(ToTokens::to_token_stream) - .collect(); - - let general = quote! { - const #bits_ident: usize = #bits; - const #offset_ident: usize = #offset; - - #doc - #[doc = #location] - #vis fn #set_ident(&mut self, value: #ty) { - *self = self.#with_ident(value); - } - }; - - let mask: u128 = !0 >> (u128::BITS as usize - bits); - let mask = syn::LitInt::new(&format!("0x{mask:x}"), Span::mixed_site()); +#[cfg(test)] +mod test { + use std::fmt; - let code = match class { - TypeClass::Bool => quote! { - #general + use super::bitfield; - #doc - #[doc = #location] - #vis const fn #with_ident(self, value: #ty) -> Self { - Self(self.0 & !(1 << #offset) | (value as #base_ty) << #offset) - } - #doc - #[doc = #location] - #vis const fn #ident(&self) -> #ty { - ((self.0 >> #offset) & 1) != 0 - } - }, - TypeClass::Int | TypeClass::SizeInt => quote! { - #general - - #doc - #[doc = #location] - #vis const fn #with_ident(self, value: #ty) -> Self { - debug_assert!(value <= #mask); - Self(self.0 & !(#mask << #offset) | (value as #base_ty & #mask) << #offset) - } - #doc - #[doc = #location] - #vis const fn #ident(&self) -> #ty { - let shift = #ty::BITS as usize - #bits; - (((self.0 >> #offset) as #ty) << shift) >> shift - } - }, - TypeClass::Other => quote! { - #general - - #doc - #[doc = #location] - #vis fn #with_ident(self, value: #ty) -> Self { - let value: #base_ty = value.into(); - debug_assert!(value <= #mask); - Self(self.0 & !(#mask << #offset) | (value & #mask) << #offset) - } - #doc - #[doc = #location] - #vis fn #ident(&self) -> #ty { - let shift = #base_ty::BITS as usize - #bits; - (((self.0 >> #offset) << shift) >> shift).into() - } - }, - }; - tokens.extend(code); + #[test] + fn copy_bits() { + // single byte + let src = &[0b00111000]; + let dst = &mut [0b10001111]; + super::bit_copy(dst, 4, src, 3, 3); + assert_eq!(dst, &[0b11111111]); + // reversed + let src = &[!0b00111000]; + let dst = &mut [!0b10001111]; + super::bit_copy(dst, 4, src, 3, 3); + assert_eq!(dst, &[!0b11111111]); + + // two to single byte + let src = &[0b00000000, 0b11000000, 0b00000111, 0b00000000]; + let dst = &mut [0b00000000, 0b00000000, 0b00000000, 0b00000000]; + super::bit_copy(dst, 17, src, 14, 5); + assert_eq!(dst, &[0b00000000, 0b00000000, 0b00111110, 0b0000000]); + // reversed + let src = &[!0b00000000, !0b11000000, !0b00000111, !0b00000000]; + let dst = &mut [!0b00000000, !0b00000000, !0b00000000, !0b00000000]; + super::bit_copy(dst, 17, src, 14, 5); + assert_eq!(dst, &[!0b00000000, !0b00000000, !0b00111110, !0b0000000]); + + // over two bytes + let src = &[0b00000000, 0b11000000, 0b00000111, 0b00000000]; + let dst = &mut [0b00000000, 0b00000000, 0b00000000, 0b00000000]; + super::bit_copy(dst, 23, src, 14, 5); + assert_eq!(dst, &[0b00000000, 0b00000000, 0b10000000, 0b00001111]); + // reversed + let src = &[!0b00000000, !0b11000000, !0b00000111, !0b00000000]; + let dst = &mut [!0b00000000, !0b00000000, !0b00000000, !0b00000000]; + super::bit_copy(dst, 23, src, 14, 5); + assert_eq!(dst, &[!0b00000000, !0b00000000, !0b10000000, !0b00001111]); + + // over three bytes + let src = &[0b11000000, 0b11111111, 0b00000111, 0b00000000]; + let dst = &mut [0b00000000, 0b00000000, 0b00000000, 0b00000000]; + super::bit_copy(dst, 15, src, 6, 13); + assert_eq!(dst, &[0b00000000, 0b10000000, 0b11111111, 0b00001111]); + // reversed + let src = &[!0b11000000, !0b11111111, !0b00000111, !0b00000000]; + let dst = &mut [!0b00000000, !0b00000000, !0b00000000, !0b00000000]; + super::bit_copy(dst, 15, src, 6, 13); + assert_eq!(dst, &[!0b00000000, !0b10000000, !0b11111111, !0b00001111]); } -} -/// Parses the `bits` attribute that allows specifying a custom number of bits. -fn bits(attrs: &[syn::Attribute], ty: &syn::Type) -> syn::Result<(TypeClass, usize)> { - fn malformed(mut e: syn::Error, attr: &syn::Attribute) -> syn::Error { - e.combine(syn::Error::new(attr.span(), "malformed #[bits] attribute")); - e - } + #[test] + fn members() { + /// A test bitfield with documentation + #[bitfield(u64)] + struct MyBitfield { + /// defaults to 16 bits for u16 + int: u16, + /// interpreted as 1 bit flag + flag: bool, + /// custom bit size + #[bits(1)] + tiny: u8, + /// sign extend for signed integers + #[bits(13)] + negative: i16, + /// supports any type that implements `From` and `Into` + #[bits(16)] + custom: CustomEnum, + /// public field -> public accessor functions + #[bits(12)] + pub public: usize, + /// padding + #[bits(5)] + _p: u8, + /// zero-sized members are ignored + #[bits(0)] + _completely_ignored: String, + } - for attr in attrs { - match attr { - syn::Attribute { - style: syn::AttrStyle::Outer, - path, - tokens, - .. - } if path.is_ident("bits") => { - let bits = attr - .parse_args::() - .map_err(|e| malformed(e, attr))? - .base10_parse() - .map_err(|e| malformed(e, attr))?; - - return if bits == 0 { - Ok((TypeClass::Other, 0)) - } else if let Ok((class, size)) = type_bits(ty) { - if bits <= size { - Ok((class, bits)) - } else { - Err(syn::Error::new(tokens.span(), "overflowing field type")) - } - } else if matches!(ty, syn::Type::Path(syn::TypePath{ path, .. }) - if path.is_ident("usize") || path.is_ident("isize")) - { - // isize and usize are supported but types size is not known at this point! - // Meaning that they must have a bits attribute explicitly defining their size - Ok((TypeClass::SizeInt, bits)) - } else { - Ok((TypeClass::Other, bits)) - }; + /// A custom enum + #[derive(Debug, PartialEq, Eq)] + #[repr(u64)] + enum CustomEnum { + A = 0, + B = 1, + C = 2, + } + impl From for CustomEnum { + fn from(value: u64) -> Self { + match value { + 0 => Self::A, + 1 => Self::B, + _ => Self::C, + } } - _ => {} } - } - - if let syn::Type::Path(syn::TypePath { path, .. }) = ty { - if path.is_ident("usize") || path.is_ident("isize") { - return Err(syn::Error::new( - ty.span(), - "isize and usize fields require the #[bits($1)] attribute", - )); + impl From for u64 { + fn from(value: CustomEnum) -> Self { + value as _ + } } - } - // Fallback to type size - type_bits(ty) -} + let mut val = MyBitfield::new() + .with_int(3 << 15) + .with_flag(true) + .with_tiny(1) + .with_negative(-3) + .with_custom(CustomEnum::B) + .with_public(2); -/// Returns the number of bits for a given type -fn type_bits(ty: &syn::Type) -> syn::Result<(TypeClass, usize)> { - let syn::Type::Path(syn::TypePath{ path, .. }) = ty else { - return Err(syn::Error::new(ty.span(), "unsupported type")) - }; - let Some(ident) = path.get_ident() else { - return Err(syn::Error::new(ty.span(), "unsupported type")) - }; - if ident == "bool" { - return Ok((TypeClass::Bool, 1)); - } - macro_rules! integer { - ($ident:ident => $($ty:ident),*) => { - match ident { - $(_ if ident == stringify!($ty) => Ok((TypeClass::Int, $ty::BITS as _)),)* - _ => Err(syn::Error::new(ty.span(), "unsupported type")) - } - }; - } - integer!(ident => u8, i8, u16, i16, u32, i32, u64, i64, u128, i128) -} - -struct Params { - ty: syn::Type, - bits: usize, - debug: bool, -} + println!("{val:?}"); -impl Parse for Params { - fn parse(input: ParseStream) -> syn::Result { - let Ok(ty) = syn::Type::parse(input) else { - return Err(syn::Error::new(input.span(), "unknown type")); - }; - let (class, bits) = type_bits(&ty)?; - if class != TypeClass::Int { - return Err(syn::Error::new(input.span(), "unsupported type")); - } + let raw: u64 = val.into(); + println!("{raw:b}"); - // try parse additional debug arg - let debug = if ::parse(input).is_ok() { - let ident = Ident::parse(input)?; + assert_eq!(val.int(), 3 << 15); + assert_eq!(val.flag(), true); + assert_eq!(val.negative(), -3); + assert_eq!(val.tiny(), 1); + assert_eq!(val.custom(), CustomEnum::B); + assert_eq!(val.public(), 2); - if ident != "debug" { - return Err(syn::Error::new(ident.span(), "unknown argument")); - } - ::parse(input)?; + // const members + assert_eq!(MyBitfield::FLAG_BITS, 1); + assert_eq!(MyBitfield::FLAG_OFFSET, 16); - syn::LitBool::parse(input)?.value - } else { - true - }; + val.set_negative(1); + assert_eq!(val.negative(), 1); - Ok(Params { bits, ty, debug }) + let pte = val.with_flag(false); + assert_eq!(pte.flag(), false); } -} -fn unsupported_param(mut e: syn::Error, arg: T) -> syn::Error -where - T: syn::spanned::Spanned, -{ - e.combine(syn::Error::new( - arg.span(), - "unsupported #[bitfield] argument", - )); - e -} + #[test] + fn attrs() { + /// We have a custom debug implementation -> opt out + #[bitfield(u64)] + #[derive(PartialEq, Eq, Default)] + struct Full { + data: u64, + } -#[cfg(test)] -mod test { - use quote::quote; + let full = Full::default(); + assert_eq!(u64::from(full), u64::from(Full::new())); - use crate::Params; + let full = Full::new().with_data(u64::MAX); + assert_eq!(full.data(), u64::MAX); + assert!(full == Full::new().with_data(u64::MAX)); + } #[test] - fn parse_args() { - let args = quote! { - u64 - }; - let params = syn::parse2::(args).unwrap(); - assert!(params.bits == u64::BITS as usize && params.debug == true); - - let args = quote! { - u32, debug = false - }; - let params = syn::parse2::(args).unwrap(); - assert!(params.bits == u32::BITS as usize && params.debug == false); + fn debug() { + /// We have a custom debug implementation -> opt out + #[bitfield(u64, debug = false)] + struct Full { + data: u64, + } + + impl fmt::Debug for Full { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "0x{:x}", self.data()) + } + } + + let full = Full::new().with_data(123); + println!("{full:?}"); } } diff --git a/tests/test.rs b/tests/test.rs deleted file mode 100644 index f1f725b..0000000 --- a/tests/test.rs +++ /dev/null @@ -1,121 +0,0 @@ -use std::fmt; - -use bitfield_struct::bitfield; - -#[test] -fn members() { - /// A test bitfield with documentation - #[bitfield(u64)] - struct MyBitfield { - /// defaults to 16 bits for u16 - int: u16, - /// interpreted as 1 bit flag - flag: bool, - /// custom bit size - #[bits(1)] - tiny: u8, - /// sign extend for signed integers - #[bits(13)] - negative: i16, - /// supports any type that implements `From` and `Into` - #[bits(16)] - custom: CustomEnum, - /// public field -> public accessor functions - #[bits(12)] - pub public: usize, - /// padding - #[bits(5)] - _p: u8, - /// zero-sized members are ignored - #[bits(0)] - _completely_ignored: String, - } - - /// A custom enum - #[derive(Debug, PartialEq, Eq)] - #[repr(u64)] - enum CustomEnum { - A = 0, - B = 1, - C = 2, - } - impl From for CustomEnum { - fn from(value: u64) -> Self { - match value { - 0 => Self::A, - 1 => Self::B, - _ => Self::C, - } - } - } - impl From for u64 { - fn from(value: CustomEnum) -> Self { - value as _ - } - } - - let mut val = MyBitfield::new() - .with_int(3 << 15) - .with_flag(true) - .with_tiny(1) - .with_negative(-3) - .with_custom(CustomEnum::B) - .with_public(2); - - println!("{val:?}"); - - let raw: u64 = val.into(); - println!("{raw:b}"); - - assert_eq!(val.int(), 3 << 15); - assert_eq!(val.flag(), true); - assert_eq!(val.negative(), -3); - assert_eq!(val.tiny(), 1); - assert_eq!(val.custom(), CustomEnum::B); - assert_eq!(val.public(), 2); - - // const members - assert_eq!(MyBitfield::FLAG_BITS, 1); - assert_eq!(MyBitfield::FLAG_OFFSET, 16); - - val.set_negative(1); - assert_eq!(val.negative(), 1); - - let pte = val.with_flag(false); - assert_eq!(pte.flag(), false); -} - -#[test] -fn attrs() { - /// We have a custom debug implementation -> opt out - #[bitfield(u64)] - #[derive(PartialEq, Eq, Default)] - struct Full { - data: u64, - } - - let full = Full::default(); - assert_eq!(u64::from(full), u64::from(Full::new())); - - let full = Full::new().with_data(u64::MAX); - assert_eq!(full.data(), u64::MAX); - assert!(full == Full::new().with_data(u64::MAX)); -} - -#[test] -fn debug() { - /// We have a custom debug implementation -> opt out - #[bitfield(u64, debug = false)] - struct Full { - data: u64, - } - - impl fmt::Debug for Full { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "0x{:x}", self.data()) - } - } - - let full = Full::new().with_data(123); - println!("{full:?}"); -} From 121df217ac0b827098e99720efce27ada88fb653 Mon Sep 17 00:00:00 2001 From: Lars Wrenger Date: Fri, 3 Feb 2023 20:37:13 +0100 Subject: [PATCH 2/9] :construction: Refactoring the bitcopy routines --- src/lib.rs | 175 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 114 insertions(+), 61 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f6f4a03..c238f2e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,37 +1,68 @@ pub use bitfield_struct_derive::bitfield; -/// The heart of the bitfield macro +/// The heart of the bitfield macro. +/// It copies bits (with different offsets) from `src` to `dst`. /// -/// General idea. -/// - Copy prefix, suffix bits. +/// This function is used both for the getters and setters of the bitfield struct. +/// +/// General idea: +/// - Copy prefix bits /// - Copy aligned u8 -/// - Copy aligned u16 for the inner array -/// - Copy aligned u32 for the inner inner array -/// - Copy aligned u64 for the inner inner inner array +/// - Copy suffix bits +/// +/// Possible future optimization: +/// - Copy and shift with larger instructions (u16/u32/u64) if the buffers are large enough /// /// FIXME: Use mutable reference as soon as `const_mut_refs` is stable -#[inline] +#[inline(always)] pub fn bit_copy(dst: &mut [u8], dst_off: usize, src: &[u8], src_off: usize, len: usize) { - println!("copy {dst:x?}, {dst_off}, {src:x?}, {src_off}, {len}"); debug_assert!(len > 0); - debug_assert!(dst.len() * 8 - dst_off >= len); - debug_assert!(src.len() * 8 - src_off >= len); + debug_assert!(dst.len() * 8 >= dst_off + len); + debug_assert!(src.len() * 8 >= src_off + len); - let mut len = len; - let mut dst = &mut dst[dst_off / 8..]; - let mut src = &src[src_off / 8..]; + // normalize input + let dst = &mut dst[dst_off / 8..]; + let src = &src[src_off / 8..]; let dst_off = dst_off % 8; - let mut src_off = src_off % 8; + let src_off = src_off % 8; if len < (8 - dst_off) { + // edge case if there are less then one byte to be copied single_byte(&mut dst[0], dst_off, src, src_off, len); - return; + } else if dst_off == src_off { + copy_aligned(dst, src, dst_off, len); + } else { + copy_unaligned(dst, dst_off, src, src_off, len); } +} + +#[inline(always)] +fn single_byte(dst: &mut u8, dst_off: usize, src: &[u8], src_off: usize, len: usize) { + let mask = (u8::MAX >> (8 - len)) << dst_off; + *dst &= !mask; + *dst |= ((src[0] >> src_off) << dst_off) & mask; - // copy prefix + // exceeding a single byte of the src buffer + if len + src_off > 8 { + *dst |= (src[1] << (8 - src_off + dst_off)) & mask; + } +} + +#[inline(always)] +fn copy_unaligned( + mut dst: &mut [u8], + dst_off: usize, + mut src: &[u8], + mut src_off: usize, + mut len: usize, +) { + debug_assert!(0 < dst_off && dst_off < 8 && 0 < src_off && src_off < 8); + debug_assert!(dst.len() * 8 >= dst_off + len); + debug_assert!(src.len() * 8 >= src_off + len); + + // copy dst prefix -> byte-align dst if dst_off > 0 { let prefix = 8 - dst_off; - println!("copy prefix {prefix}"); let mask = u8::MAX << dst_off; dst[0] &= !mask; dst[0] |= (src[0] >> src_off) << dst_off; @@ -49,46 +80,16 @@ pub fn bit_copy(dst: &mut [u8], dst_off: usize, src: &[u8], src_off: usize, len: len -= prefix; } - if src_off == 0 { - copy_aligned(dst, src, len); - } else { - copy_dst_aligned(dst, src, src_off, len); - } -} - -fn single_byte(dst: &mut u8, dst_off: usize, src: &[u8], src_off: usize, len: usize) { - println!("copy small"); - - let mask = (u8::MAX >> (8 - len)) << dst_off; - *dst &= !mask; - *dst |= ((src[0] >> src_off) << dst_off) & mask; - - // exceeding a single byte of the src buffer - if len + src_off > 8 { - *dst |= (src[1] << (8 - src_off + dst_off)) & mask; - } -} - -#[inline] -fn copy_dst_aligned(dst: &mut [u8], src: &[u8], src_off: usize, len: usize) { - println!("dst_aligned {dst:?}, {src:?}, {src_off}, {len}"); - debug_assert!(0 < src_off && src_off < 8); - debug_assert!(dst.len() * 8 >= len); - debug_assert!(src.len() * 8 - src_off >= len); // has to be one larger - // copy middle for i in 0..len / 8 { dst[i] = (src[i] >> src_off) | (src[i + 1] << (8 - src_off)); } - println!("middle {dst:x?}"); // suffix let suffix = len % 8; if suffix > 0 { - println!("copy suffix {suffix}"); let last = len / 8; let mask = u8::MAX >> (8 - suffix); - dst[last] &= !mask; dst[last] |= src[last] >> src_off; @@ -98,24 +99,34 @@ fn copy_dst_aligned(dst: &mut [u8], src: &[u8], src_off: usize, len: usize) { } } } -#[inline] -fn copy_aligned(dst: &mut [u8], src: &[u8], len: usize) { - println!("aligned {dst:?}, {src:?}, {len}"); - debug_assert!(dst.len() * 8 >= len); - debug_assert!(src.len() * 8 >= len); +#[inline(always)] +fn copy_aligned(mut dst: &mut [u8], mut src: &[u8], off: usize, mut len: usize) { + debug_assert!(0 < off && off < 8); + debug_assert!(dst.len() * 8 >= off + len); + debug_assert!(src.len() * 8 >= off + len); + + // copy prefix -> byte-align dst + if off > 0 { + let prefix = 8 - off; + let mask = u8::MAX << off; + dst[0] &= !mask; + dst[0] |= src[0] & mask; - // copy middle - for i in 0..len / 8 { - dst[i] = src[i]; + src = &src[1..]; + dst = &mut dst[1..]; + len -= prefix; } - // suffix + + // copy middle + let bytes = len / 8; + dst[..bytes].copy_from_slice(&src[..bytes]); + + // copy suffix let suffix = len % 8; if suffix > 0 { - println!("copy suffix {suffix}"); - let last = len / 8; - let mask = u8::MAX >> suffix; - dst[last] &= mask; + let mask = u8::MAX >> (8 - suffix); + dst[last] &= !mask; dst[last] |= src[last]; } } @@ -127,7 +138,21 @@ mod test { use super::bitfield; #[test] - fn copy_bits() { + fn copy_bits_single_bit() { + // single byte + let src = &[0b00100000]; + let dst = &mut [0b10111111]; + super::bit_copy(dst, 6, src, 5, 1); + assert_eq!(dst, &[0b11111111]); + // reversed + let src = &[!0b00100000]; + let dst = &mut [!0b10111111]; + super::bit_copy(dst, 6, src, 5, 1); + assert_eq!(dst, &[!0b11111111]); + } + + #[test] + fn copy_bits_single_byte() { // single byte let src = &[0b00111000]; let dst = &mut [0b10001111]; @@ -138,7 +163,10 @@ mod test { let dst = &mut [!0b10001111]; super::bit_copy(dst, 4, src, 3, 3); assert_eq!(dst, &[!0b11111111]); + } + #[test] + fn copy_bits_unaligned() { // two to single byte let src = &[0b00000000, 0b11000000, 0b00000111, 0b00000000]; let dst = &mut [0b00000000, 0b00000000, 0b00000000, 0b00000000]; @@ -173,6 +201,31 @@ mod test { assert_eq!(dst, &[!0b00000000, !0b10000000, !0b11111111, !0b00001111]); } + #[test] + fn copy_bits_aligned() { + // over two bytes + let src = &[0b00000000, 0b11000000, 0b00000111, 0b00000000]; + let dst = &mut [0b00000000, 0b00000000, 0b00000000, 0b00000000]; + super::bit_copy(dst, 14, src, 14, 5); + assert_eq!(dst, src); + // reversed + let src = &[!0b00000000, !0b11000000, !0b00000111, !0b00000000]; + let dst = &mut [!0b00000000, !0b00000000, !0b00000000, !0b00000000]; + super::bit_copy(dst, 14, src, 14, 5); + assert_eq!(dst, src); + + // over three bytes + let src = &[0b11000000, 0b11100111, 0b00000111, 0b00000000]; + let dst = &mut [0b00000000, 0b00000000, 0b00000000, 0b00000000]; + super::bit_copy(dst, 14, src, 6, 13); + assert_eq!(dst, &[0b00000000, 0b11000000, 0b11100111, 0b00000111]); + // reversed + let src = &[!0b11000000, !0b11100111, !0b00000111, !0b00000000]; + let dst = &mut [!0b00000000, !0b00000000, !0b00000000, !0b00000000]; + super::bit_copy(dst, 14, src, 6, 13); + assert_eq!(dst, &[!0b00000000, !0b11000000, !0b11100111, !0b00000111]); + } + #[test] fn members() { /// A test bitfield with documentation From ff76a9dd19e313f2cd87bb11e5a64eab45899e5c Mon Sep 17 00:00:00 2001 From: Lars Wrenger Date: Sat, 4 Feb 2023 18:53:25 +0100 Subject: [PATCH 3/9] :art: Const transformation Const mutable references are not stable yet, thus we fall back on moving the buffers in and out directly. The downside is additinal copies, even for optimized code. --- src/lib.rs | 247 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 147 insertions(+), 100 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c238f2e..37417e5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,74 +15,109 @@ pub use bitfield_struct_derive::bitfield; /// /// FIXME: Use mutable reference as soon as `const_mut_refs` is stable #[inline(always)] -pub fn bit_copy(dst: &mut [u8], dst_off: usize, src: &[u8], src_off: usize, len: usize) { +pub const fn bit_copy( + mut dst: [u8; D], + dst_off: usize, + src: &[u8], + src_off: usize, + len: usize, +) -> [u8; D] { debug_assert!(len > 0); debug_assert!(dst.len() * 8 >= dst_off + len); debug_assert!(src.len() * 8 >= src_off + len); - // normalize input - let dst = &mut dst[dst_off / 8..]; - let src = &src[src_off / 8..]; - let dst_off = dst_off % 8; - let src_off = src_off % 8; - - if len < (8 - dst_off) { + if len == 1 { + let dst_i = dst_off / 8; + dst[dst_i] = single_bit(dst[dst_i], dst_off % 8, src, src_off); + dst + } else if len < (8 - (dst_off % 8)) { // edge case if there are less then one byte to be copied - single_byte(&mut dst[0], dst_off, src, src_off, len); - } else if dst_off == src_off { - copy_aligned(dst, src, dst_off, len); + let dst_i = dst_off / 8; + dst[dst_i] = single_byte(dst[dst_i], dst_off % 8, src, src_off, len); + dst + } else if dst_off % 8 == src_off % 8 { + copy_aligned(dst, dst_off / 8, src, src_off / 8, dst_off % 8, len) + } else { + copy_unaligned(dst, dst_off, src, src_off, len) + } +} + +#[inline(always)] +pub const fn is_bit_set(src: &[u8], i: usize) -> bool { + debug_assert!(i < src.len() * 8); + (src[i / 8] >> (i % 8)) & 1 != 0 +} + +#[inline(always)] +const fn single_bit(dst: u8, dst_off: usize, src: &[u8], src_off: usize) -> u8 { + debug_assert!(dst_off < 8); + if is_bit_set(src, src_off) { + dst | (1 << dst_off) } else { - copy_unaligned(dst, dst_off, src, src_off, len); + dst & !(1 << dst_off) } } #[inline(always)] -fn single_byte(dst: &mut u8, dst_off: usize, src: &[u8], src_off: usize, len: usize) { +const fn single_byte(dst: u8, dst_off: usize, src: &[u8], src_off: usize, len: usize) -> u8 { + debug_assert!(dst_off < 8); + + let src_i = src_off / 8; + let src_off = src_off % 8; + let mask = (u8::MAX >> (8 - len)) << dst_off; - *dst &= !mask; - *dst |= ((src[0] >> src_off) << dst_off) & mask; + let mut dst = dst & !mask; + dst |= ((src[src_i] >> src_off) << dst_off) & mask; // exceeding a single byte of the src buffer if len + src_off > 8 { - *dst |= (src[1] << (8 - src_off + dst_off)) & mask; + dst |= (src[src_i + 1] << (8 - src_off + dst_off)) & mask; } + dst } #[inline(always)] -fn copy_unaligned( - mut dst: &mut [u8], - dst_off: usize, - mut src: &[u8], +const fn copy_unaligned( + mut dst: [u8; D], + mut dst_off: usize, + src: &[u8], mut src_off: usize, mut len: usize, -) { - debug_assert!(0 < dst_off && dst_off < 8 && 0 < src_off && src_off < 8); +) -> [u8; D] { + debug_assert!(src_off % 8 != 0 && dst_off % 8 != 0); debug_assert!(dst.len() * 8 >= dst_off + len); debug_assert!(src.len() * 8 >= src_off + len); + let mut dst_i = dst_off / 8; + dst_off %= 8; + let mut src_i = src_off / 8; + src_off %= 8; + // copy dst prefix -> byte-align dst if dst_off > 0 { let prefix = 8 - dst_off; let mask = u8::MAX << dst_off; - dst[0] &= !mask; - dst[0] |= (src[0] >> src_off) << dst_off; + dst[dst_i] &= !mask; + dst[dst_i] |= (src[src_i] >> src_off) << dst_off; // exceeding a single byte of the src buffer src_off += prefix; if let Some(reminder) = src_off.checked_sub(8) { - src = &src[1..]; + src_i += 1; if reminder > 0 { - dst[0] |= src[0] << (dst_off + reminder) + dst[dst_i] |= src[src_i] << (dst_off + reminder) } src_off = reminder } - dst = &mut dst[1..]; + dst_i += 1; len -= prefix; } // copy middle - for i in 0..len / 8 { - dst[i] = (src[i] >> src_off) | (src[i + 1] << (8 - src_off)); + let mut i = 0; + while i < len / 8 { + dst[dst_i + i] = (src[src_i + i] >> src_off) | (src[src_i + i + 1] << (8 - src_off)); + i += 1; } // suffix @@ -90,45 +125,57 @@ fn copy_unaligned( if suffix > 0 { let last = len / 8; let mask = u8::MAX >> (8 - suffix); - dst[last] &= !mask; - dst[last] |= src[last] >> src_off; + dst[dst_i + last] &= !mask; + dst[dst_i + last] |= src[src_i + last] >> src_off; // exceeding a single byte of the src buffer if suffix + src_off > 8 { - dst[last] |= (src[last + 1] << (8 - src_off)) & mask; + dst[dst_i + last] |= (src[src_i + last + 1] << (8 - src_off)) & mask; } } + dst } #[inline(always)] -fn copy_aligned(mut dst: &mut [u8], mut src: &[u8], off: usize, mut len: usize) { - debug_assert!(0 < off && off < 8); - debug_assert!(dst.len() * 8 >= off + len); - debug_assert!(src.len() * 8 >= off + len); +const fn copy_aligned( + mut dst: [u8; D], + mut dst_i: usize, + src: &[u8], + mut src_i: usize, + off: usize, + mut len: usize, +) -> [u8; D] { + debug_assert!(off < 8); + debug_assert!(dst.len() * 8 >= dst_i * 8 + len); + debug_assert!(src.len() * 8 >= src_i * 8 + len); // copy prefix -> byte-align dst if off > 0 { - let prefix = 8 - off; - let mask = u8::MAX << off; - dst[0] &= !mask; - dst[0] |= src[0] & mask; + let prefix = 8 - (off % 8); + let mask = u8::MAX << (off % 8); + dst[dst_i] &= !mask; + dst[dst_i] |= src[src_i] & mask; - src = &src[1..]; - dst = &mut dst[1..]; + src_i += 1; + dst_i += 1; len -= prefix; } // copy middle - let bytes = len / 8; - dst[..bytes].copy_from_slice(&src[..bytes]); + let mut i = 0; + while i < len / 8 { + dst[dst_i + i] = src[src_i + i]; + i += 1; + } // copy suffix let suffix = len % 8; if suffix > 0 { let last = len / 8; let mask = u8::MAX >> (8 - suffix); - dst[last] &= !mask; - dst[last] |= src[last]; + dst[dst_i + last] &= !mask; + dst[dst_i + last] |= src[src_i + last]; } + dst } #[cfg(test)] @@ -140,90 +187,90 @@ mod test { #[test] fn copy_bits_single_bit() { // single byte - let src = &[0b00100000]; - let dst = &mut [0b10111111]; - super::bit_copy(dst, 6, src, 5, 1); - assert_eq!(dst, &[0b11111111]); + let src = [0b00100000]; + let dst = [0b10111111]; + let dst = super::bit_copy(dst, 6, &src, 5, 1); + assert_eq!(dst, [0b11111111]); // reversed - let src = &[!0b00100000]; - let dst = &mut [!0b10111111]; - super::bit_copy(dst, 6, src, 5, 1); - assert_eq!(dst, &[!0b11111111]); + let src = [!0b00100000]; + let dst = [!0b10111111]; + let dst = super::bit_copy(dst, 6, &src, 5, 1); + assert_eq!(dst, [!0b11111111]); } #[test] fn copy_bits_single_byte() { // single byte - let src = &[0b00111000]; - let dst = &mut [0b10001111]; - super::bit_copy(dst, 4, src, 3, 3); - assert_eq!(dst, &[0b11111111]); + let src = [0b00111000]; + let dst = [0b10001111]; + let dst = super::bit_copy(dst, 4, &src, 3, 3); + assert_eq!(dst, [0b11111111]); // reversed - let src = &[!0b00111000]; - let dst = &mut [!0b10001111]; - super::bit_copy(dst, 4, src, 3, 3); - assert_eq!(dst, &[!0b11111111]); + let src = [!0b00111000]; + let dst = [!0b10001111]; + let dst = super::bit_copy(dst, 4, &src, 3, 3); + assert_eq!(dst, [!0b11111111]); } #[test] fn copy_bits_unaligned() { // two to single byte - let src = &[0b00000000, 0b11000000, 0b00000111, 0b00000000]; - let dst = &mut [0b00000000, 0b00000000, 0b00000000, 0b00000000]; - super::bit_copy(dst, 17, src, 14, 5); - assert_eq!(dst, &[0b00000000, 0b00000000, 0b00111110, 0b0000000]); + let src = [0b00000000, 0b11000000, 0b00000111, 0b00000000]; + let dst = [0b00000000, 0b00000000, 0b00000000, 0b00000000]; + let dst = super::bit_copy(dst, 17, &src, 14, 5); + assert_eq!(dst, [0b00000000, 0b00000000, 0b00111110, 0b0000000]); // reversed - let src = &[!0b00000000, !0b11000000, !0b00000111, !0b00000000]; - let dst = &mut [!0b00000000, !0b00000000, !0b00000000, !0b00000000]; - super::bit_copy(dst, 17, src, 14, 5); - assert_eq!(dst, &[!0b00000000, !0b00000000, !0b00111110, !0b0000000]); + let src = [!0b00000000, !0b11000000, !0b00000111, !0b00000000]; + let dst = [!0b00000000, !0b00000000, !0b00000000, !0b00000000]; + let dst = super::bit_copy(dst, 17, &src, 14, 5); + assert_eq!(dst, [!0b00000000, !0b00000000, !0b00111110, !0b0000000]); // over two bytes - let src = &[0b00000000, 0b11000000, 0b00000111, 0b00000000]; - let dst = &mut [0b00000000, 0b00000000, 0b00000000, 0b00000000]; - super::bit_copy(dst, 23, src, 14, 5); - assert_eq!(dst, &[0b00000000, 0b00000000, 0b10000000, 0b00001111]); + let src = [0b00000000, 0b11000000, 0b00000111, 0b00000000]; + let dst = [0b00000000, 0b00000000, 0b00000000, 0b00000000]; + let dst = super::bit_copy(dst, 23, &src, 14, 5); + assert_eq!(dst, [0b00000000, 0b00000000, 0b10000000, 0b00001111]); // reversed - let src = &[!0b00000000, !0b11000000, !0b00000111, !0b00000000]; - let dst = &mut [!0b00000000, !0b00000000, !0b00000000, !0b00000000]; - super::bit_copy(dst, 23, src, 14, 5); - assert_eq!(dst, &[!0b00000000, !0b00000000, !0b10000000, !0b00001111]); + let src = [!0b00000000, !0b11000000, !0b00000111, !0b00000000]; + let dst = [!0b00000000, !0b00000000, !0b00000000, !0b00000000]; + let dst = super::bit_copy(dst, 23, &src, 14, 5); + assert_eq!(dst, [!0b00000000, !0b00000000, !0b10000000, !0b00001111]); // over three bytes - let src = &[0b11000000, 0b11111111, 0b00000111, 0b00000000]; - let dst = &mut [0b00000000, 0b00000000, 0b00000000, 0b00000000]; - super::bit_copy(dst, 15, src, 6, 13); - assert_eq!(dst, &[0b00000000, 0b10000000, 0b11111111, 0b00001111]); + let src = [0b11000000, 0b11111111, 0b00000111, 0b00000000]; + let dst = [0b00000000, 0b00000000, 0b00000000, 0b00000000]; + let dst = super::bit_copy(dst, 15, &src, 6, 13); + assert_eq!(dst, [0b00000000, 0b10000000, 0b11111111, 0b00001111]); // reversed - let src = &[!0b11000000, !0b11111111, !0b00000111, !0b00000000]; - let dst = &mut [!0b00000000, !0b00000000, !0b00000000, !0b00000000]; - super::bit_copy(dst, 15, src, 6, 13); - assert_eq!(dst, &[!0b00000000, !0b10000000, !0b11111111, !0b00001111]); + let src = [!0b11000000, !0b11111111, !0b00000111, !0b00000000]; + let dst = [!0b00000000, !0b00000000, !0b00000000, !0b00000000]; + let dst = super::bit_copy(dst, 15, &src, 6, 13); + assert_eq!(dst, [!0b00000000, !0b10000000, !0b11111111, !0b00001111]); } #[test] fn copy_bits_aligned() { // over two bytes - let src = &[0b00000000, 0b11000000, 0b00000111, 0b00000000]; - let dst = &mut [0b00000000, 0b00000000, 0b00000000, 0b00000000]; - super::bit_copy(dst, 14, src, 14, 5); + let src = [0b00000000, 0b11000000, 0b00000111, 0b00000000]; + let dst = [0b00000000, 0b00000000, 0b00000000, 0b00000000]; + let dst = super::bit_copy(dst, 14, &src, 14, 5); assert_eq!(dst, src); // reversed - let src = &[!0b00000000, !0b11000000, !0b00000111, !0b00000000]; - let dst = &mut [!0b00000000, !0b00000000, !0b00000000, !0b00000000]; - super::bit_copy(dst, 14, src, 14, 5); + let src = [!0b00000000, !0b11000000, !0b00000111, !0b00000000]; + let dst = [!0b00000000, !0b00000000, !0b00000000, !0b00000000]; + let dst = super::bit_copy(dst, 14, &src, 14, 5); assert_eq!(dst, src); // over three bytes - let src = &[0b11000000, 0b11100111, 0b00000111, 0b00000000]; - let dst = &mut [0b00000000, 0b00000000, 0b00000000, 0b00000000]; - super::bit_copy(dst, 14, src, 6, 13); - assert_eq!(dst, &[0b00000000, 0b11000000, 0b11100111, 0b00000111]); + let src = [0b11000000, 0b11100111, 0b00000111, 0b00000000]; + let dst = [0b00000000, 0b00000000, 0b00000000, 0b00000000]; + let dst = super::bit_copy(dst, 14, &src, 6, 13); + assert_eq!(dst, [0b00000000, 0b11000000, 0b11100111, 0b00000111]); // reversed - let src = &[!0b11000000, !0b11100111, !0b00000111, !0b00000000]; - let dst = &mut [!0b00000000, !0b00000000, !0b00000000, !0b00000000]; - super::bit_copy(dst, 14, src, 6, 13); - assert_eq!(dst, &[!0b00000000, !0b11000000, !0b11100111, !0b00000111]); + let src = [!0b11000000, !0b11100111, !0b00000111, !0b00000000]; + let dst = [!0b00000000, !0b00000000, !0b00000000, !0b00000000]; + let dst = super::bit_copy(dst, 14, &src, 6, 13); + assert_eq!(dst, [!0b00000000, !0b11000000, !0b11100111, !0b00000111]); } #[test] From b7ac13a664c7aaffe6e82495a9d47cc445f94499 Mon Sep 17 00:00:00 2001 From: Lars Wrenger Date: Fri, 17 Feb 2023 18:32:52 +0100 Subject: [PATCH 4/9] :sparkles: Code generation using bit_copy --- derive/src/lib.rs | 163 ++++++++++++++++++++++++++++++++-------------- src/lib.rs | 152 +++++++++--------------------------------- tests/test.rs | 145 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 291 insertions(+), 169 deletions(-) create mode 100644 tests/test.rs diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 3e9b085..7f30ad5 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -231,9 +231,14 @@ pub fn bitfield(args: pc::TokenStream, input: pc::TokenStream) -> pc::TokenStrea fn bitfield_inner(args: TokenStream, input: TokenStream) -> syn::Result { let input = syn::parse2::(input)?; - let Params { ty, bits, debug } = - syn::parse2::(args).map_err(|e| unsupported_param(e, input.span()))?; - + let Params { + bytes, + align, + debug, + ty, + } = syn::parse2::(args).map_err(|e| unsupported_param(e, input.span()))?; + + let bits = bytes * 8; let span = input.fields.span(); let name = input.ident; let name_str = name.to_string(); @@ -247,7 +252,7 @@ fn bitfield_inner(args: TokenStream, input: TokenStream) -> syn::Result syn::Result for #name { + fn from(v: #ty) -> Self { + Self(v.to_be_bytes()) + } + } + impl From<#name> for #ty { + fn from(v: #name) -> #ty { + #ty::from_be_bytes(v.0) + } + } + }) + } else { + None + }; + let align = syn::LitInt::new(&format!("{align}"), Span::mixed_site()); Ok(quote! { #attrs #[derive(Copy, Clone)] - #[repr(transparent)] - #vis struct #name(#ty); + #[repr(align(#align))] + #vis struct #name([u8; #bytes]); impl #name { #vis const fn new() -> Self { - Self(0) + Self([0; #bytes]) } #( #members )* } - impl From<#ty> for #name { - fn from(v: #ty) -> Self { + impl From<[u8; #bytes]> for #name { + fn from(v: [u8; #bytes]) -> Self { Self(v) } } - impl From<#name> for #ty { - fn from(v: #name) -> #ty { + impl From<#name> for [u8; #bytes] { + fn from(v: #name) -> [u8; #bytes] { v.0 } } + #type_conversion #( #const_asserts )* @@ -348,7 +371,6 @@ enum TypeClass { } struct Member { - base_ty: syn::Type, attrs: Vec, ty: syn::Type, class: TypeClass, @@ -359,7 +381,7 @@ struct Member { } impl Member { - fn new(base_ty: syn::Type, f: syn::Field, offset: usize) -> syn::Result { + fn new(f: syn::Field, offset: usize) -> syn::Result { let span = f.span(); let syn::Field { @@ -377,7 +399,6 @@ impl Member { attrs.retain(|a| !a.path.is_ident("bits")); Ok(Self { - base_ty, attrs, ty, class, @@ -402,7 +423,6 @@ impl Member { impl ToTokens for Member { fn to_tokens(&self, tokens: &mut TokenStream) { let Self { - base_ty, attrs, ty, class, @@ -442,8 +462,7 @@ impl ToTokens for Member { } }; - let mask: u128 = !0 >> (u128::BITS as usize - bits); - let mask = syn::LitInt::new(&format!("0x{mask:x}"), Span::mixed_site()); + let bytes = (bits + 7) / 8; let code = match class { TypeClass::Bool => quote! { @@ -452,12 +471,13 @@ impl ToTokens for Member { #doc #[doc = #location] #vis const fn #with_ident(self, value: #ty) -> Self { - Self(self.0 & !(1 << #offset) | (value as #base_ty) << #offset) + let src = [value as u8]; + Self(bitfield_struct::bit_copy(self.0, #offset, &src, 0, 1)) } #doc #[doc = #location] #vis const fn #ident(&self) -> #ty { - ((self.0 >> #offset) & 1) != 0 + bitfield_struct::is_bit_set(&self.0, #offset) } }, TypeClass::Int | TypeClass::SizeInt => quote! { @@ -466,14 +486,22 @@ impl ToTokens for Member { #doc #[doc = #location] #vis const fn #with_ident(self, value: #ty) -> Self { - debug_assert!(value <= #mask); - Self(self.0 & !(#mask << #offset) | (value as #base_ty & #mask) << #offset) + #[cfg(target_endian = "little")] + let src = value.to_le_bytes(); + #[cfg(target_endian = "big")] + let src = value.to_be_bytes(); + Self(bitfield_struct::bit_copy(self.0, #offset, &src, 0, #bits)) } #doc #[doc = #location] #vis const fn #ident(&self) -> #ty { - let shift = #ty::BITS as usize - #bits; - (((self.0 >> #offset) as #ty) << shift) >> shift + let out = bitfield_struct::bit_copy( + [0; #ty::BITS as usize / 8], #ty::BITS as usize - #bits, &self.0, #offset, #bits); + #[cfg(target_endian = "little")] + let out = #ty::from_le_bytes(out); + #[cfg(target_endian = "big")] + let out = #ty::from_be_bytes(out); + out >> (#ty::BITS as usize - #bits) } }, TypeClass::Other => quote! { @@ -482,15 +510,14 @@ impl ToTokens for Member { #doc #[doc = #location] #vis fn #with_ident(self, value: #ty) -> Self { - let value: #base_ty = value.into(); - debug_assert!(value <= #mask); - Self(self.0 & !(#mask << #offset) | (value & #mask) << #offset) + let src: [u8; #bytes] = value.into(); + Self(bitfield_struct::bit_copy(self.0, #offset, &src, 0, #bits)) } #doc #[doc = #location] #vis fn #ident(&self) -> #ty { - let shift = #base_ty::BITS as usize - #bits; - (((self.0 >> #offset) << shift) >> shift).into() + let out = bitfield_struct::bit_copy([0; #bytes], 0, &self.0, #offset, #bits); + out.into() } }, }; @@ -577,36 +604,76 @@ fn type_bits(ty: &syn::Type) -> syn::Result<(TypeClass, usize)> { } struct Params { - ty: syn::Type, - bits: usize, + ty: Option, + bytes: usize, + align: usize, debug: bool, } impl Parse for Params { fn parse(input: ParseStream) -> syn::Result { - let Ok(ty) = syn::Type::parse(input) else { - return Err(syn::Error::new(input.span(), "unknown type")); - }; - let (class, bits) = type_bits(&ty)?; - if class != TypeClass::Int { - return Err(syn::Error::new(input.span(), "unsupported type")); + let mut bytes = 1; + let mut align = 1; + let mut debug = true; + let mut ty = None; + + let params = syn::punctuated::Punctuated::::parse_terminated(input)?; + for (i, param) in params.into_iter().enumerate() { + match param { + Param::Type(t, bits) => { + if i != 0 { + return Err(syn::Error::new( + input.span(), + "the `ty` argument has to be the first", + )); + } + ty = Some(t); + bytes = bits / 8; + align = bits / 8; + } + Param::Bytes(b) => bytes = b, + Param::Align(a) => align = a, + Param::Debug(d) => debug = d, + } } - // try parse additional debug arg - let debug = if ::parse(input).is_ok() { - let ident = Ident::parse(input)?; + Ok(Params { + ty, + bytes, + align, + debug, + }) + } +} - if ident != "debug" { - return Err(syn::Error::new(ident.span(), "unknown argument")); - } - ::parse(input)?; +enum Param { + Type(syn::Type, usize), + Bytes(usize), + Align(usize), + Debug(bool), +} - syn::LitBool::parse(input)?.value +impl Parse for Param { + fn parse(input: ParseStream) -> syn::Result { + let ident = Ident::parse(input)?; + ::parse(input)?; + + if ident == "ty" { + let ty = syn::Type::parse(input)?; + let (class, bits) = type_bits(&ty)?; + if class != TypeClass::Int { + return Err(syn::Error::new(input.span(), "unsupported type")); + } + Ok(Self::Type(ty, bits)) + } else if ident == "bytes" { + Ok(Self::Bytes(syn::LitInt::parse(input)?.base10_parse()?)) + } else if ident == "align" { + Ok(Self::Align(syn::LitInt::parse(input)?.base10_parse()?)) + } else if ident == "debug" { + Ok(Self::Debug(syn::LitBool::parse(input)?.value)) } else { - true - }; - - Ok(Params { bits, ty, debug }) + Err(syn::Error::new(ident.span(), "unknown argument")) + } } } diff --git a/src/lib.rs b/src/lib.rs index 37417e5..9bff59d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -84,7 +84,7 @@ const fn copy_unaligned( mut src_off: usize, mut len: usize, ) -> [u8; D] { - debug_assert!(src_off % 8 != 0 && dst_off % 8 != 0); + debug_assert!(src_off % 8 != 0 || dst_off % 8 != 0); debug_assert!(dst.len() * 8 >= dst_off + len); debug_assert!(src.len() * 8 >= src_off + len); @@ -101,11 +101,12 @@ const fn copy_unaligned( dst[dst_i] |= (src[src_i] >> src_off) << dst_off; // exceeding a single byte of the src buffer + dst_off += 8 - src_off; src_off += prefix; if let Some(reminder) = src_off.checked_sub(8) { src_i += 1; if reminder > 0 { - dst[dst_i] |= src[src_i] << (dst_off + reminder) + dst[dst_i] |= src[src_i] << dst_off } src_off = reminder } @@ -180,9 +181,14 @@ const fn copy_aligned( #[cfg(test)] mod test { - use std::fmt; - use super::bitfield; + #[allow(unused)] + fn b_print(b: &[u8]) { + for v in b.iter().rev() { + print!("{v:08b} "); + } + println!() + } #[test] fn copy_bits_single_bit() { @@ -246,6 +252,17 @@ mod test { let dst = [!0b00000000, !0b00000000, !0b00000000, !0b00000000]; let dst = super::bit_copy(dst, 15, &src, 6, 13); assert_eq!(dst, [!0b00000000, !0b10000000, !0b11111111, !0b00001111]); + + // prefix exceeds a single byte + let src = [0b00000000, 0b10000000, 0b11111111, 0b00000111]; + let dst = [0b00000000, 0b00000000, 0b00000000, 0b00000000]; + let dst = super::bit_copy(dst, 20, &src, 15, 12); + assert_eq!(dst, [0b00000000, 0b00000000, 0b11110000, 0b11111111]); + // reversed + let src = [!0b00000000, !0b10000000, !0b11111111, !0b00000111]; + let dst = [!0b00000000, !0b00000000, !0b00000000, !0b00000000]; + let dst = super::bit_copy(dst, 20, &src, 15, 12); + assert_eq!(dst, [!0b00000000, !0b00000000, !0b11110000, !0b11111111]); } #[test] @@ -271,123 +288,16 @@ mod test { let dst = [!0b00000000, !0b00000000, !0b00000000, !0b00000000]; let dst = super::bit_copy(dst, 14, &src, 6, 13); assert_eq!(dst, [!0b00000000, !0b11000000, !0b11100111, !0b00000111]); - } - - #[test] - fn members() { - /// A test bitfield with documentation - #[bitfield(u64)] - struct MyBitfield { - /// defaults to 16 bits for u16 - int: u16, - /// interpreted as 1 bit flag - flag: bool, - /// custom bit size - #[bits(1)] - tiny: u8, - /// sign extend for signed integers - #[bits(13)] - negative: i16, - /// supports any type that implements `From` and `Into` - #[bits(16)] - custom: CustomEnum, - /// public field -> public accessor functions - #[bits(12)] - pub public: usize, - /// padding - #[bits(5)] - _p: u8, - /// zero-sized members are ignored - #[bits(0)] - _completely_ignored: String, - } - - /// A custom enum - #[derive(Debug, PartialEq, Eq)] - #[repr(u64)] - enum CustomEnum { - A = 0, - B = 1, - C = 2, - } - impl From for CustomEnum { - fn from(value: u64) -> Self { - match value { - 0 => Self::A, - 1 => Self::B, - _ => Self::C, - } - } - } - impl From for u64 { - fn from(value: CustomEnum) -> Self { - value as _ - } - } - - let mut val = MyBitfield::new() - .with_int(3 << 15) - .with_flag(true) - .with_tiny(1) - .with_negative(-3) - .with_custom(CustomEnum::B) - .with_public(2); - - println!("{val:?}"); - - let raw: u64 = val.into(); - println!("{raw:b}"); - assert_eq!(val.int(), 3 << 15); - assert_eq!(val.flag(), true); - assert_eq!(val.negative(), -3); - assert_eq!(val.tiny(), 1); - assert_eq!(val.custom(), CustomEnum::B); - assert_eq!(val.public(), 2); - - // const members - assert_eq!(MyBitfield::FLAG_BITS, 1); - assert_eq!(MyBitfield::FLAG_OFFSET, 16); - - val.set_negative(1); - assert_eq!(val.negative(), 1); - - let pte = val.with_flag(false); - assert_eq!(pte.flag(), false); - } - - #[test] - fn attrs() { - /// We have a custom debug implementation -> opt out - #[bitfield(u64)] - #[derive(PartialEq, Eq, Default)] - struct Full { - data: u64, - } - - let full = Full::default(); - assert_eq!(u64::from(full), u64::from(Full::new())); - - let full = Full::new().with_data(u64::MAX); - assert_eq!(full.data(), u64::MAX); - assert!(full == Full::new().with_data(u64::MAX)); - } - - #[test] - fn debug() { - /// We have a custom debug implementation -> opt out - #[bitfield(u64, debug = false)] - struct Full { - data: u64, - } - - impl fmt::Debug for Full { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "0x{:x}", self.data()) - } - } - - let full = Full::new().with_data(123); - println!("{full:?}"); + // all bits + let src = [0xff, 0xff, 0xff, 0xff]; + let dst = [0, 0, 0, 0]; + let dst = super::bit_copy(dst, 0, &src, 0, 4 * 8); + assert_eq!(dst, [0xff, 0xff, 0xff, 0xff]); + // reversed + let src = [0, 0, 0, 0]; + let dst = [0xff, 0xff, 0xff, 0xff]; + let dst = super::bit_copy(dst, 0, &src, 0, 4 * 8); + assert_eq!(dst, [0, 0, 0, 0]); } } diff --git a/tests/test.rs b/tests/test.rs new file mode 100644 index 0000000..5e64af4 --- /dev/null +++ b/tests/test.rs @@ -0,0 +1,145 @@ +use std::fmt; +use std::mem::{align_of, size_of}; + +use bitfield_struct::bitfield; + +#[test] +fn members() { + /// A test bitfield with documentation + #[bitfield(ty = u64)] + struct MyBitfield { + /// defaults to 16 bits for u16 + int: u16, + /// interpreted as 1 bit flag + flag: bool, + /// custom bit size + #[bits(1)] + tiny: u8, + /// sign extend for signed integers + #[bits(13)] + negative: i16, + /// supports any type that implements `From` and `Into` + #[bits(16)] + custom: CustomEnum, + /// public field -> public accessor functions + #[bits(12)] + pub public: usize, + /// padding + #[bits(5)] + _p: u8, + /// zero-sized members are ignored + #[bits(0)] + _completely_ignored: String, + } + + /// A custom enum + #[derive(Debug, PartialEq, Eq)] + #[repr(u8)] + enum CustomEnum { + A = 0, + B = 1, + C = 2, + } + impl From<[u8; 2]> for CustomEnum { + fn from(value: [u8; 2]) -> Self { + match value[0] { + 0 => Self::A, + 1 => Self::B, + _ => Self::C, + } + } + } + impl From for [u8; 2] { + fn from(value: CustomEnum) -> Self { + [value as _, 0] + } + } + + let mut val = MyBitfield::new() + .with_int(3 << 15) + .with_flag(true) + .with_tiny(1) + .with_negative(-3) + .with_custom(CustomEnum::B) + .with_public((1 << MyBitfield::PUBLIC_BITS) - 1); + + println!("{val:?}"); + + let raw: u64 = val.into(); + println!("{raw:b}"); + + let raw: [u8; 8] = val.into(); + for v in raw { + print!("{v:08b} "); + } + println!(); + + assert_eq!(val.int(), 3 << 15); + assert_eq!(val.flag(), true); + assert_eq!(val.negative(), -3); + assert_eq!(val.tiny(), 1); + assert_eq!(val.custom(), CustomEnum::B); + assert_eq!(val.public(), (1 << MyBitfield::PUBLIC_BITS) - 1); + + // const members + assert_eq!(MyBitfield::FLAG_BITS, 1); + assert_eq!(MyBitfield::FLAG_OFFSET, 16); + + val.set_negative(1); + assert_eq!(val.negative(), 1); + + let pte = val.with_flag(false); + assert_eq!(pte.flag(), false); +} + +#[test] +fn attrs() { + /// We have a custom debug implementation -> opt out + #[bitfield(ty = u64)] + #[derive(PartialEq, Eq, Default)] + struct Full { + data: u64, + } + + let full = Full::default(); + assert_eq!(u64::from(full), u64::from(Full::new())); + + let full = Full::new().with_data(u64::MAX); + assert_eq!(full.data(), u64::MAX); + assert!(full == Full::new().with_data(u64::MAX)); +} + +#[test] +fn debug() { + /// We have a custom debug implementation -> opt out + #[bitfield(ty = u64, debug = false)] + struct Full { + data: u64, + } + + impl fmt::Debug for Full { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "0x{:x}", self.data()) + } + } + + let full = Full::new().with_data(123); + println!("{full:?}"); +} + +#[test] +fn custom_size() { + /// We have a custom debug implementation -> opt out + #[bitfield(bytes = 9)] + struct NoTy { + data: u64, + extra: u8, + } + + let full = NoTy::new().with_data(123).with_extra(255); + assert_eq!(full.data(), 123); + assert_eq!(full.extra(), 255); + assert_eq!(align_of::(), 1); + assert_eq!(size_of::(), 9); + println!("{full:?}"); +} From 0ec807583d3a98d48a823afcaab1b996332cc758 Mon Sep 17 00:00:00 2001 From: Lars Wrenger Date: Fri, 17 Feb 2023 18:48:32 +0100 Subject: [PATCH 5/9] :memo: Update docs --- derive/src/lib.rs | 223 +++------------------------------------------ src/lib.rs | 228 ++++++++++++++++++++++++++++++++++++++++++++++ tests/test.rs | 4 +- 3 files changed, 242 insertions(+), 213 deletions(-) diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 7f30ad5..5173a59 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -1,211 +1,3 @@ -//! # Bitfield Struct -//! -//! Procedural macro for bitfields that allows specifying bitfields as structs. -//! As this library provides a procedural-macro it has no runtime dependencies and works for `no-std`. -//! -//! - Supports bool flags, raw integers, and every custom type convertible into integers (structs/enums) -//! - Ideal for driver/OS/embedded development (defining HW registers/structures) -//! - Generates minimalistic, pure, safe rust functions -//! - Compile-time checks for type and field sizes -//! - Rust-analyzer friendly (carries over documentation to accessor functions) -//! - Exports field offsets and sizes as constants (useful for const asserts) -//! - Generation of `fmt::Debug` -//! -//! ## Basics -//! -//! Let's begin with a simple example.
-//! Suppose we want to store multiple data inside a single Byte, as shown below: -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//!
76543310
PLevelSKind
-//! -//! This crate is able to generate a nice wrapper type that makes it easy to do this: -//! -//! ``` -//! # use bitfield_struct::bitfield; -//! /// Define your type like this with the bitfield attribute -//! #[bitfield(u8)] -//! struct MyByte { -//! /// The first field occupies the least significant bits -//! #[bits(4)] -//! kind: usize, -//! /// Booleans are 1 bit large -//! system: bool, -//! /// The bits attribute specifies the bit size of this field -//! #[bits(2)] -//! level: usize, -//! /// The last field spans over the most significant bits -//! present: bool -//! } -//! // The macro creates three accessor functions for each field: -//! // , with_ and set_ -//! let my_byte = MyByte::new() -//! .with_kind(15) -//! .with_system(false) -//! .with_level(3) -//! .with_present(true); -//! -//! assert!(my_byte.present()); -//! ``` -//! -//! ## Features -//! -//! Additionally, this crate has a few useful features, which are shown here in more detail. -//! -//! The example below shows how attributes are carried over and how signed integers, padding, and custom types are handled. -//! -//! ``` -//! # use bitfield_struct::bitfield; -//! /// A test bitfield with documentation -//! #[bitfield(u64)] -//! #[derive(PartialEq, Eq)] // <- Attributes after `bitfield` are carried over -//! struct MyBitfield { -//! /// defaults to 16 bits for u16 -//! int: u16, -//! /// interpreted as 1 bit flag -//! flag: bool, -//! /// custom bit size -//! #[bits(1)] -//! tiny: u8, -//! /// sign extend for signed integers -//! #[bits(13)] -//! negative: i16, -//! /// supports any type that implements `From` and `Into` -//! #[bits(16)] -//! custom: CustomEnum, -//! /// public field -> public accessor functions -//! #[bits(12)] -//! pub public: usize, -//! /// padding -//! #[bits(5)] -//! _p: u8, -//! /// zero-sized members are ignored -//! #[bits(0)] -//! _completely_ignored: String, -//! } -//! -//! /// A custom enum -//! #[derive(Debug, PartialEq, Eq)] -//! #[repr(u64)] -//! enum CustomEnum { -//! A = 0, -//! B = 1, -//! C = 2, -//! } -//! // implement `From` and `Into` for `CustomEnum`! -//! # impl From for CustomEnum { -//! # fn from(value: u64) -> Self { -//! # match value { -//! # 0 => Self::A, -//! # 1 => Self::B, -//! # _ => Self::C, -//! # } -//! # } -//! # } -//! # impl From for u64 { -//! # fn from(value: CustomEnum) -> Self { -//! # value as _ -//! # } -//! # } -//! -//! // Usage: -//! let mut val = MyBitfield::new() -//! .with_int(3 << 15) -//! .with_flag(true) -//! .with_tiny(1) -//! .with_negative(-3) -//! .with_custom(CustomEnum::B) -//! .with_public(2); -//! -//! println!("{val:?}"); -//! let raw: u64 = val.into(); -//! println!("{raw:b}"); -//! -//! assert_eq!(val.int(), 3 << 15); -//! assert_eq!(val.flag(), true); -//! assert_eq!(val.negative(), -3); -//! assert_eq!(val.tiny(), 1); -//! assert_eq!(val.custom(), CustomEnum::B); -//! assert_eq!(val.public(), 2); -//! -//! // const members -//! assert_eq!(MyBitfield::FLAG_BITS, 1); -//! assert_eq!(MyBitfield::FLAG_OFFSET, 16); -//! -//! val.set_negative(1); -//! assert_eq!(val.negative(), 1); -//! ``` -//! -//! The macro generates three accessor functions for each field. -//! Each accessor also inherits the documentation of its field. -//! -//! The signatures for `int` are: -//! -//! ```ignore -//! // generated struct -//! struct MyBitfield(u64); -//! impl MyBitfield { -//! const fn new() -> Self { Self(0) } -//! -//! const INT_BITS: usize = 16; -//! const INT_OFFSET: usize = 0; -//! -//! const fn with_int(self, value: u16) -> Self { /* ... */ } -//! const fn int(&self) -> u16 { /* ... */ } -//! fn set_int(&mut self, value: u16) { /* ... */ } -//! -//! // other field ... -//! } -//! // generated trait implementations -//! impl From for MyBitfield { /* ... */ } -//! impl From for u64 { /* ... */ } -//! impl Debug for MyBitfield { /* ... */ } -//! ``` -//! -//! > Hint: You can use the rust-analyzer "Expand macro recursively" action to view the generated code. -//! -//! ## `fmt::Debug` -//! -//! This macro automatically creates a suitable `fmt::Debug` implementation -//! similar to the ones created for normal structs by `#[derive(Debug)]`. -//! You can disable it with the extra debug argument. -//! -//! ``` -//! # use std::fmt; -//! # use bitfield_struct::bitfield; -//! -//! #[bitfield(u64, debug = false)] -//! struct CustomDebug { -//! data: u64 -//! } -//! -//! impl fmt::Debug for CustomDebug { -//! fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -//! write!(f, "0x{:x}", self.data()) -//! } -//! } -//! -//! let val = CustomDebug::new().with_data(123); -//! println!("{val:?}") -//! ``` -//! - use proc_macro as pc; use proc_macro2::{Ident, Span, TokenStream}; use quote::{format_ident, quote, ToTokens}; @@ -216,11 +8,18 @@ use syn::Token; /// Creates a bitfield for this struct. /// -/// The arguments first, have to begin with the underlying type of the bitfield: -/// For example: `#[bitfield(u64)]`. +/// Integer bitfields start with the `ty` argument followed by an integer type. +/// The size and alignment are copied from the integer type, but can be overwritten. +/// The can be omitted if you want to specify the size and alignment of the bitfield manually. +/// +/// For example: `#[bitfield(ty = u64)]`. +/// +/// The other arguments, this macro accepts are: +/// - `bytes`: The byte size of the bitfield (default: 1) +/// - `align`: The alignment of the bitfield (default: 1) +/// - `debug`: Whether or not the fmt::Debug trait should be generated (default: true) /// -/// It can contain an extra `debug` argument for disabling the `Debug` trait -/// generation (`#[bitfield(u64, debug = false)]`). +/// For example: `#[bitfield(bytes = 6, align = 2, debug = false)]` #[proc_macro_attribute] pub fn bitfield(args: pc::TokenStream, input: pc::TokenStream) -> pc::TokenStream { match bitfield_inner(args.into(), input.into()) { diff --git a/src/lib.rs b/src/lib.rs index 9bff59d..8bc3441 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,231 @@ +//! # Bitfield Struct +//! +//! Procedural macro for bitfields that allows specifying bitfields as structs. +//! As this library provides a procedural-macro it has no runtime dependencies and works for `no-std`. +//! +//! - Supports bool flags, raw integers, and every custom type convertible into integers (structs/enums) +//! - Ideal for driver/OS/embedded development (defining HW registers/structures) +//! - Generates minimalistic, pure, safe rust functions +//! - Compile-time checks for type and field sizes +//! - Rust-analyzer friendly (carries over documentation to accessor functions) +//! - Exports field offsets and sizes as constants (useful for const asserts) +//! - Generation of `fmt::Debug` +//! +//! ## Basics +//! +//! Let's begin with a simple example.
+//! Suppose we want to store multiple data inside a single Byte, as shown below: +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//!
76543310
PLevelSKind
+//! +//! This crate is able to generate a nice wrapper type that makes it easy to do this: +//! +//! ``` +//! # use bitfield_struct::bitfield; +//! /// Define your type like this with the bitfield attribute +//! #[bitfield(ty = u8)] +//! struct MyByte { +//! /// The first field occupies the least significant bits +//! #[bits(4)] +//! kind: usize, +//! /// Booleans are 1 bit large +//! system: bool, +//! /// The bits attribute specifies the bit size of this field +//! #[bits(2)] +//! level: usize, +//! /// The last field spans over the most significant bits +//! present: bool +//! } +//! // The macro creates three accessor functions for each field: +//! // , with_ and set_ +//! let my_byte = MyByte::new() +//! .with_kind(15) +//! .with_system(false) +//! .with_level(3) +//! .with_present(true); +//! +//! assert!(my_byte.present()); +//! ``` +//! +//! ## Features +//! +//! Additionally, this crate has a few useful features, which are shown here in more detail. +//! +//! The example below shows how attributes are carried over and how signed integers, padding, and custom types are handled. +//! +//! ``` +//! # use bitfield_struct::bitfield; +//! /// A test bitfield with documentation +//! #[bitfield(ty = u64)] +//! #[derive(PartialEq, Eq)] // <- Attributes after `bitfield` are carried over +//! struct MyBitfield { +//! /// defaults to 16 bits for u16 +//! int: u16, +//! /// interpreted as 1 bit flag +//! flag: bool, +//! /// custom bit size +//! #[bits(1)] +//! tiny: u8, +//! /// sign extend for signed integers +//! #[bits(13)] +//! negative: i16, +//! /// supports any type that implements `From` and `Into` +//! #[bits(16)] +//! custom: CustomEnum, +//! /// public field -> public accessor functions +//! #[bits(12)] +//! pub public: usize, +//! /// padding +//! #[bits(5)] +//! _p: u8, +//! /// zero-sized members are ignored +//! #[bits(0)] +//! _completely_ignored: String, +//! } +//! +//! /// A custom enum +//! #[derive(Debug, PartialEq, Eq)] +//! #[repr(u8)] +//! enum CustomEnum { +//! A = 0, +//! B = 1, +//! C = 2, +//! } +//! // implement `From<[u8; 2]>` and `Into<[u8; 2]>` for `CustomEnum`! +//! # impl From<[u8; 2]> for CustomEnum { +//! # fn from(value: [u8; 2]) -> Self { +//! # match value[0] { +//! # 0 => Self::A, +//! # 1 => Self::B, +//! # _ => Self::C, +//! # } +//! # } +//! # } +//! # impl From for [u8; 2] { +//! # fn from(value: CustomEnum) -> Self { +//! # [value as _, 0] +//! # } +//! # } +//! +//! // Usage: +//! let mut val = MyBitfield::new() +//! .with_int(3 << 15) +//! .with_flag(true) +//! .with_tiny(1) +//! .with_negative(-3) +//! .with_custom(CustomEnum::B) +//! .with_public(2); +//! +//! println!("{val:?}"); +//! let raw: u64 = val.into(); +//! println!("{raw:b}"); +//! +//! assert_eq!(val.int(), 3 << 15); +//! assert_eq!(val.flag(), true); +//! assert_eq!(val.negative(), -3); +//! assert_eq!(val.tiny(), 1); +//! assert_eq!(val.custom(), CustomEnum::B); +//! assert_eq!(val.public(), 2); +//! +//! // const members +//! assert_eq!(MyBitfield::FLAG_BITS, 1); +//! assert_eq!(MyBitfield::FLAG_OFFSET, 16); +//! +//! val.set_negative(1); +//! assert_eq!(val.negative(), 1); +//! ``` +//! +//! The macro generates three accessor functions for each field. +//! Each accessor also inherits the documentation of its field. +//! +//! The signatures for `int` are: +//! +//! ```ignore +//! // generated struct +//! struct MyBitfield([u8; 8]); +//! impl MyBitfield { +//! const fn new() -> Self { Self([0; 8]) } +//! +//! const INT_BITS: usize = 16; +//! const INT_OFFSET: usize = 0; +//! +//! const fn with_int(self, value: u16) -> Self { /* ... */ } +//! const fn int(&self) -> u16 { /* ... */ } +//! fn set_int(&mut self, value: u16) { /* ... */ } +//! +//! // other field ... +//! } +//! // generated trait implementations +//! impl From<[u8; 8]> for MyBitfield { /* ... */ } +//! impl From for [u8; 8] { /* ... */ } +//! // from the `ty` parameter +//! impl From for MyBitfield { /* ... */ } +//! impl From for u64 { /* ... */ } +//! impl Debug for MyBitfield { /* ... */ } +//! ``` +//! +//! > Hint: You can use the rust-analyzer "Expand macro recursively" action to view the generated code. +//! +//! ## No-type bitfields +//! +//! Instead of specifying a base type, you can manually define the `size` and `align` of the bitfield. +//! +//! ``` +//! # use bitfield_struct::bitfield; +//! # use std::mem::{size_of, align_of}; +//! #[bitfield(bytes = 9)] +//! struct NoTy { +//! data: u64, +//! extra: u8, +//! } +//! assert_eq!(size_of::(), 9); +//! assert_eq!(align_of::(), 1); // align defaults to 1 +//! ``` +//! +//! ## `fmt::Debug` +//! +//! This macro automatically creates a suitable `fmt::Debug` implementation +//! similar to the ones created for normal structs by `#[derive(Debug)]`. +//! You can disable it with the extra debug argument. +//! +//! ``` +//! # use std::fmt; +//! # use bitfield_struct::bitfield; +//! +//! #[bitfield(ty = u64, debug = false)] +//! struct CustomDebug { +//! data: u64 +//! } +//! +//! impl fmt::Debug for CustomDebug { +//! fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +//! write!(f, "0x{:x}", self.data()) +//! } +//! } +//! +//! let val = CustomDebug::new().with_data(123); +//! println!("{val:?}") +//! ``` +//! + + pub use bitfield_struct_derive::bitfield; /// The heart of the bitfield macro. diff --git a/tests/test.rs b/tests/test.rs index 5e64af4..2430c29 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -55,6 +55,9 @@ fn members() { } } + assert_eq!(align_of::(), 8); + assert_eq!(size_of::(), 8); + let mut val = MyBitfield::new() .with_int(3 << 15) .with_flag(true) @@ -129,7 +132,6 @@ fn debug() { #[test] fn custom_size() { - /// We have a custom debug implementation -> opt out #[bitfield(bytes = 9)] struct NoTy { data: u64, From 9726fb66137657caa359cbf45aaad1df7db2a418 Mon Sep 17 00:00:00 2001 From: Lars Wrenger Date: Fri, 17 Feb 2023 19:04:19 +0100 Subject: [PATCH 6/9] :art: Test no args --- derive/src/lib.rs | 6 +++--- tests/test.rs | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 5173a59..3d1db43 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -9,12 +9,12 @@ use syn::Token; /// Creates a bitfield for this struct. /// /// Integer bitfields start with the `ty` argument followed by an integer type. -/// The size and alignment are copied from the integer type, but can be overwritten. -/// The can be omitted if you want to specify the size and alignment of the bitfield manually. +/// The size and alignment are copied from the integer type but can be overwritten. +/// The `ty` argument can be omitted if you want to specify the size and alignment of the bitfield manually. /// /// For example: `#[bitfield(ty = u64)]`. /// -/// The other arguments, this macro accepts are: +/// The other arguments for this macro accepts are: /// - `bytes`: The byte size of the bitfield (default: 1) /// - `align`: The alignment of the bitfield (default: 1) /// - `debug`: Whether or not the fmt::Debug trait should be generated (default: true) diff --git a/tests/test.rs b/tests/test.rs index 2430c29..e13ae91 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -145,3 +145,17 @@ fn custom_size() { assert_eq!(size_of::(), 9); println!("{full:?}"); } + +#[test] +fn no_args() { + #[bitfield] + struct NoArgs { + data: u8, + } + + let full = NoArgs::new().with_data(123); + assert_eq!(full.data(), 123); + assert_eq!(align_of::(), 1); + assert_eq!(size_of::(), 1); + println!("{full:?}"); +} From 3cda5ea05d7fc454b19586515a879cf36dc789eb Mon Sep 17 00:00:00 2001 From: Lars Wrenger Date: Mon, 20 Feb 2023 11:12:52 +0100 Subject: [PATCH 7/9] :bug: Typos --- derive/src/lib.rs | 2 +- src/lib.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 3d1db43..0bc9aee 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -14,7 +14,7 @@ use syn::Token; /// /// For example: `#[bitfield(ty = u64)]`. /// -/// The other arguments for this macro accepts are: +/// The other arguments for this macro are: /// - `bytes`: The byte size of the bitfield (default: 1) /// - `align`: The alignment of the bitfield (default: 1) /// - `debug`: Whether or not the fmt::Debug trait should be generated (default: true) diff --git a/src/lib.rs b/src/lib.rs index 8bc3441..eadfc5a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -225,7 +225,6 @@ //! ``` //! - pub use bitfield_struct_derive::bitfield; /// The heart of the bitfield macro. From 1225814ab29101724d5c991fcb62d1134f94ad12 Mon Sep 17 00:00:00 2001 From: Lars Wrenger Date: Mon, 27 Feb 2023 23:35:22 +0100 Subject: [PATCH 8/9] :sparkles: New bitfield syntax --- .gitignore | 5 +- Cargo.lock | 8 ++-- derive/src/lib.rs | 118 ++++++++++++++++++++++++++-------------------- src/lib.rs | 6 +-- tests/test.rs | 20 ++------ 5 files changed, 82 insertions(+), 75 deletions(-) diff --git a/.gitignore b/.gitignore index ea8c4bf..bc4f9f5 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ -/target +target + +.DS_Store +*~* diff --git a/Cargo.lock b/Cargo.lock index 2652889..90affd8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,9 +23,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.49" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" dependencies = [ "unicode-ident", ] @@ -41,9 +41,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.107" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 0bc9aee..3da395c 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -3,20 +3,22 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::{format_ident, quote, ToTokens}; use std::stringify; use syn::parse::{Parse, ParseStream}; +use syn::punctuated::Punctuated; use syn::spanned::Spanned; use syn::Token; /// Creates a bitfield for this struct. /// -/// Integer bitfields start with the `ty` argument followed by an integer type. -/// The size and alignment are copied from the integer type but can be overwritten. -/// The `ty` argument can be omitted if you want to specify the size and alignment of the bitfield manually. +/// There are two ways to specify the size and alignment of the bitfield: /// -/// For example: `#[bitfield(ty = u64)]`. +/// - Integer type: `#[bitfield(u32)]` +/// - All explicitly sized integers are supported +/// - The alignment defaults to the alignment of the integer if not otherwise specified +/// - Bytes argument: `#[bitfield(bytes = 3)]` +/// - The default alignment is 1 /// -/// The other arguments for this macro are: -/// - `bytes`: The byte size of the bitfield (default: 1) -/// - `align`: The alignment of the bitfield (default: 1) +/// The macro two optional arguments +/// - `align`: Specifies the alignment of the bitfield /// - `debug`: Whether or not the fmt::Debug trait should be generated (default: true) /// /// For example: `#[bitfield(bytes = 6, align = 2, debug = false)]` @@ -35,7 +37,7 @@ fn bitfield_inner(args: TokenStream, input: TokenStream) -> syn::Result(args).map_err(|e| unsupported_param(e, input.span()))?; + } = syn::parse2::(args)?; let bits = bytes * 8; let span = input.fields.span(); @@ -411,28 +413,33 @@ struct Params { impl Parse for Params { fn parse(input: ParseStream) -> syn::Result { - let mut bytes = 1; let mut align = 1; let mut debug = true; let mut ty = None; - let params = syn::punctuated::Punctuated::::parse_terminated(input)?; - for (i, param) in params.into_iter().enumerate() { - match param { - Param::Type(t, bits) => { - if i != 0 { - return Err(syn::Error::new( - input.span(), - "the `ty` argument has to be the first", - )); - } - ty = Some(t); - bytes = bits / 8; - align = bits / 8; + let bytes = if input.peek2(Token![=]) && input.peek(syn::Ident) { + Bytes::parse(input)?.bytes + } else { + let t = syn::Type::parse(input)?; + let (class, bits) = type_bits(&t)?; + if class != TypeClass::Int { + return Err(syn::Error::new( + input.span(), + "Invalid argument or type, expecting `bytes` or an integer type", + )); + } + ty = Some(t); + align = bits / 8; + bits / 8 + }; + + if let Ok(_) = ::parse(input) { + let params = Punctuated::::parse_terminated(input)?; + for param in params { + match param { + Param::Align(a) => align = a, + Param::Debug(d) => debug = d, } - Param::Bytes(b) => bytes = b, - Param::Align(a) => align = a, - Param::Debug(d) => debug = d, } } @@ -445,9 +452,27 @@ impl Parse for Params { } } +struct Bytes { + bytes: usize, +} + +impl Parse for Bytes { + fn parse(input: ParseStream) -> syn::Result { + let ident = Ident::parse(input)?; + if ident != "bytes" { + return Err(syn::Error::new( + ident.span(), + "Invalid argument, expecting `bytes` or an integer type", + )); + } + ::parse(input)?; + Ok(Self { + bytes: syn::LitInt::parse(input)?.base10_parse()?, + }) + } +} + enum Param { - Type(syn::Type, usize), - Bytes(usize), Align(usize), Debug(bool), } @@ -455,18 +480,10 @@ enum Param { impl Parse for Param { fn parse(input: ParseStream) -> syn::Result { let ident = Ident::parse(input)?; + ::parse(input)?; - if ident == "ty" { - let ty = syn::Type::parse(input)?; - let (class, bits) = type_bits(&ty)?; - if class != TypeClass::Int { - return Err(syn::Error::new(input.span(), "unsupported type")); - } - Ok(Self::Type(ty, bits)) - } else if ident == "bytes" { - Ok(Self::Bytes(syn::LitInt::parse(input)?.base10_parse()?)) - } else if ident == "align" { + if ident == "align" { Ok(Self::Align(syn::LitInt::parse(input)?.base10_parse()?)) } else if ident == "debug" { Ok(Self::Debug(syn::LitBool::parse(input)?.value)) @@ -476,17 +493,6 @@ impl Parse for Param { } } -fn unsupported_param(mut e: syn::Error, arg: T) -> syn::Error -where - T: syn::spanned::Spanned, -{ - e.combine(syn::Error::new( - arg.span(), - "unsupported #[bitfield] argument", - )); - e -} - #[cfg(test)] mod test { use quote::quote; @@ -495,16 +501,28 @@ mod test { #[test] fn parse_args() { + let args = quote! { + bytes = 3 + }; + let params = syn::parse2::(args).unwrap(); + assert!(params.bytes == 3 && params.debug == true); + + let args = quote! { + bytes = 3, align = 2, debug = false + }; + let params = syn::parse2::(args).unwrap(); + assert!(params.bytes == 3 && params.align == 2 && params.debug == false); + let args = quote! { u64 }; let params = syn::parse2::(args).unwrap(); - assert!(params.bits == u64::BITS as usize && params.debug == true); + assert!(params.bytes == 8 && params.debug == true); let args = quote! { u32, debug = false }; let params = syn::parse2::(args).unwrap(); - assert!(params.bits == u32::BITS as usize && params.debug == false); + assert!(params.bytes == 4 && params.debug == false); } } diff --git a/src/lib.rs b/src/lib.rs index eadfc5a..93ec446 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,7 +40,7 @@ //! ``` //! # use bitfield_struct::bitfield; //! /// Define your type like this with the bitfield attribute -//! #[bitfield(ty = u8)] +//! #[bitfield(u8)] //! struct MyByte { //! /// The first field occupies the least significant bits //! #[bits(4)] @@ -73,7 +73,7 @@ //! ``` //! # use bitfield_struct::bitfield; //! /// A test bitfield with documentation -//! #[bitfield(ty = u64)] +//! #[bitfield(u64, align = 4)] // <- Set a specific alignment (defaults to the integers alignment) //! #[derive(PartialEq, Eq)] // <- Attributes after `bitfield` are carried over //! struct MyBitfield { //! /// defaults to 16 bits for u16 @@ -209,7 +209,7 @@ //! # use std::fmt; //! # use bitfield_struct::bitfield; //! -//! #[bitfield(ty = u64, debug = false)] +//! #[bitfield(u64, debug = false)] //! struct CustomDebug { //! data: u64 //! } diff --git a/tests/test.rs b/tests/test.rs index e13ae91..b439106 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -6,7 +6,7 @@ use bitfield_struct::bitfield; #[test] fn members() { /// A test bitfield with documentation - #[bitfield(ty = u64)] + #[bitfield(u64)] struct MyBitfield { /// defaults to 16 bits for u16 int: u16, @@ -98,7 +98,7 @@ fn members() { #[test] fn attrs() { /// We have a custom debug implementation -> opt out - #[bitfield(ty = u64)] + #[bitfield(u64)] #[derive(PartialEq, Eq, Default)] struct Full { data: u64, @@ -115,7 +115,7 @@ fn attrs() { #[test] fn debug() { /// We have a custom debug implementation -> opt out - #[bitfield(ty = u64, debug = false)] + #[bitfield(u64, debug = false)] struct Full { data: u64, } @@ -145,17 +145,3 @@ fn custom_size() { assert_eq!(size_of::(), 9); println!("{full:?}"); } - -#[test] -fn no_args() { - #[bitfield] - struct NoArgs { - data: u8, - } - - let full = NoArgs::new().with_data(123); - assert_eq!(full.data(), 123); - assert_eq!(align_of::(), 1); - assert_eq!(size_of::(), 1); - println!("{full:?}"); -} From a2098cce22476cba1e151ff8b90b7db5ec19b74c Mon Sep 17 00:00:00 2001 From: Lars Wrenger Date: Sun, 26 Mar 2023 19:51:12 +0200 Subject: [PATCH 9/9] :sparkles: Refactoring --- derive/src/lib.rs | 132 ++++++++++++++++++++++++---------------------- src/lib.rs | 81 ++++++++++++++++------------ 2 files changed, 116 insertions(+), 97 deletions(-) diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 3da395c..27566d8 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -5,7 +5,7 @@ use std::stringify; use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; use syn::spanned::Spanned; -use syn::Token; +use syn::{Error, Token}; /// Creates a bitfield for this struct. /// @@ -47,7 +47,7 @@ fn bitfield_inner(args: TokenStream, input: TokenStream) -> syn::Result syn::Result syn::Result bits { - return Err(syn::Error::new( + return Err(Error::new( span, - format!( + format_args!( "The size of the members ({offset} bits) is larger than the type ({bits} bits)!." ), )); @@ -193,7 +193,7 @@ impl Member { .. } = f; - let ident = ident.ok_or_else(|| syn::Error::new(span, "Not supported"))?; + let ident = ident.ok_or_else(|| Error::new(span, "Not supported"))?; let (class, bits) = bits(&attrs, &ty)?; // remove our attribute @@ -287,22 +287,17 @@ impl ToTokens for Member { #doc #[doc = #location] #vis const fn #with_ident(self, value: #ty) -> Self { - #[cfg(target_endian = "little")] - let src = value.to_le_bytes(); - #[cfg(target_endian = "big")] - let src = value.to_be_bytes(); + let src = value.to_ne_bytes(); Self(bitfield_struct::bit_copy(self.0, #offset, &src, 0, #bits)) } #doc #[doc = #location] #vis const fn #ident(&self) -> #ty { + // copy to the upper half let out = bitfield_struct::bit_copy( [0; #ty::BITS as usize / 8], #ty::BITS as usize - #bits, &self.0, #offset, #bits); - #[cfg(target_endian = "little")] - let out = #ty::from_le_bytes(out); - #[cfg(target_endian = "big")] - let out = #ty::from_be_bytes(out); - out >> (#ty::BITS as usize - #bits) + // shift down to potentially perform a sign extend + #ty::from_ne_bytes(out) >> (#ty::BITS as usize - #bits) } }, TypeClass::Other => quote! { @@ -328,54 +323,50 @@ impl ToTokens for Member { /// Parses the `bits` attribute that allows specifying a custom number of bits. fn bits(attrs: &[syn::Attribute], ty: &syn::Type) -> syn::Result<(TypeClass, usize)> { - fn malformed(mut e: syn::Error, attr: &syn::Attribute) -> syn::Error { - e.combine(syn::Error::new(attr.span(), "malformed #[bits] attribute")); - e - } + let size_int = matches!(ty, syn::Type::Path(syn::TypePath{ path, .. }) + if path.is_ident("usize") || path.is_ident("isize")); for attr in attrs { - match attr { - syn::Attribute { - style: syn::AttrStyle::Outer, - path, - tokens, - .. - } if path.is_ident("bits") => { - let bits = attr - .parse_args::() - .map_err(|e| malformed(e, attr))? - .base10_parse() - .map_err(|e| malformed(e, attr))?; - - return if bits == 0 { - Ok((TypeClass::Other, 0)) - } else if let Ok((class, size)) = type_bits(ty) { - if bits <= size { - Ok((class, bits)) - } else { - Err(syn::Error::new(tokens.span(), "overflowing field type")) - } - } else if matches!(ty, syn::Type::Path(syn::TypePath{ path, .. }) - if path.is_ident("usize") || path.is_ident("isize")) - { - // isize and usize are supported but types size is not known at this point! - // Meaning that they must have a bits attribute explicitly defining their size - Ok((TypeClass::SizeInt, bits)) - } else { - Ok((TypeClass::Other, bits)) - }; + if let syn::Attribute { + style: syn::AttrStyle::Outer, + path, + tokens, + .. + } = attr + { + if !path.is_ident("bits") { + continue; } - _ => {} + + let bits = attr + .parse_args::() + .map_err(|e| e.with(Error::new(attr.span(), "malformed #[bits] attribute")))? + .base10_parse() + .map_err(|e| e.with(Error::new(attr.span(), "malformed #[bits] attribute")))?; + + return if bits == 0 { + Ok((TypeClass::Other, 0)) + } else if let Ok((class, size)) = type_bits(ty) { + if bits <= size { + Ok((class, bits)) + } else { + Err(Error::new(tokens.span(), "overflowing field type")) + } + } else if size_int { + // isize and usize are supported but their bit size is not known at this point! + // Meaning that they must have a bits attribute explicitly defining their size + Ok((TypeClass::SizeInt, bits)) + } else { + Ok((TypeClass::Other, bits)) + }; } } - if let syn::Type::Path(syn::TypePath { path, .. }) = ty { - if path.is_ident("usize") || path.is_ident("isize") { - return Err(syn::Error::new( - ty.span(), - "isize and usize fields require the #[bits($1)] attribute", - )); - } + if size_int { + return Err(Error::new( + ty.span(), + "isize and usize fields require the #[bits($1)] attribute", + )); } // Fallback to type size @@ -384,11 +375,13 @@ fn bits(attrs: &[syn::Attribute], ty: &syn::Type) -> syn::Result<(TypeClass, usi /// Returns the number of bits for a given type fn type_bits(ty: &syn::Type) -> syn::Result<(TypeClass, usize)> { + let err_unsupported = Error::new(ty.span(), "unsupported type"); + let syn::Type::Path(syn::TypePath{ path, .. }) = ty else { - return Err(syn::Error::new(ty.span(), "unsupported type")) + return Err(err_unsupported) }; let Some(ident) = path.get_ident() else { - return Err(syn::Error::new(ty.span(), "unsupported type")) + return Err(err_unsupported) }; if ident == "bool" { return Ok((TypeClass::Bool, 1)); @@ -397,7 +390,7 @@ fn type_bits(ty: &syn::Type) -> syn::Result<(TypeClass, usize)> { ($ident:ident => $($ty:ident),*) => { match ident { $(_ if ident == stringify!($ty) => Ok((TypeClass::Int, $ty::BITS as _)),)* - _ => Err(syn::Error::new(ty.span(), "unsupported type")) + _ => Err(err_unsupported) } }; } @@ -423,7 +416,7 @@ impl Parse for Params { let t = syn::Type::parse(input)?; let (class, bits) = type_bits(&t)?; if class != TypeClass::Int { - return Err(syn::Error::new( + return Err(Error::new( input.span(), "Invalid argument or type, expecting `bytes` or an integer type", )); @@ -460,7 +453,7 @@ impl Parse for Bytes { fn parse(input: ParseStream) -> syn::Result { let ident = Ident::parse(input)?; if ident != "bytes" { - return Err(syn::Error::new( + return Err(Error::new( ident.span(), "Invalid argument, expecting `bytes` or an integer type", )); @@ -488,11 +481,22 @@ impl Parse for Param { } else if ident == "debug" { Ok(Self::Debug(syn::LitBool::parse(input)?.value)) } else { - Err(syn::Error::new(ident.span(), "unknown argument")) + Err(Error::new(ident.span(), "unknown argument")) } } } +trait ErrorExt { + fn with(self, other: Self) -> Self; +} + +impl ErrorExt for Error { + fn with(mut self, other: Self) -> Self { + self.combine(other); + self + } +} + #[cfg(test)] mod test { use quote::quote; diff --git a/src/lib.rs b/src/lib.rs index 93ec446..9f0488e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -269,12 +269,14 @@ pub const fn bit_copy( } } +/// Test if this bit is set #[inline(always)] pub const fn is_bit_set(src: &[u8], i: usize) -> bool { debug_assert!(i < src.len() * 8); (src[i / 8] >> (i % 8)) & 1 != 0 } +/// Only a single bit is copied #[inline(always)] const fn single_bit(dst: u8, dst_off: usize, src: &[u8], src_off: usize) -> u8 { debug_assert!(dst_off < 8); @@ -285,24 +287,29 @@ const fn single_bit(dst: u8, dst_off: usize, src: &[u8], src_off: usize) -> u8 { } } +/// We have only one destination byte. #[inline(always)] const fn single_byte(dst: u8, dst_off: usize, src: &[u8], src_off: usize, len: usize) -> u8 { - debug_assert!(dst_off < 8); + const MAX: u8 = u8::MAX; + const BITS: usize = u8::BITS as _; + + debug_assert!(dst_off < BITS); - let src_i = src_off / 8; - let src_off = src_off % 8; + let src_i = src_off / BITS; + let src_off = src_off % BITS; - let mask = (u8::MAX >> (8 - len)) << dst_off; + let mask = (MAX >> (BITS - len)) << dst_off; let mut dst = dst & !mask; dst |= ((src[src_i] >> src_off) << dst_off) & mask; // exceeding a single byte of the src buffer - if len + src_off > 8 { - dst |= (src[src_i + 1] << (8 - src_off + dst_off)) & mask; + if len + src_off > BITS { + dst |= (src[src_i + 1] << (BITS - src_off + dst_off)) & mask; } dst } +/// The buffers have different bit offsets #[inline(always)] const fn copy_unaligned( mut dst: [u8; D], @@ -311,26 +318,29 @@ const fn copy_unaligned( mut src_off: usize, mut len: usize, ) -> [u8; D] { - debug_assert!(src_off % 8 != 0 || dst_off % 8 != 0); - debug_assert!(dst.len() * 8 >= dst_off + len); - debug_assert!(src.len() * 8 >= src_off + len); + const MAX: u8 = u8::MAX; + const BITS: usize = u8::BITS as _; - let mut dst_i = dst_off / 8; - dst_off %= 8; - let mut src_i = src_off / 8; - src_off %= 8; + debug_assert!(src_off % BITS != 0 || dst_off % BITS != 0); + debug_assert!(dst.len() * BITS >= dst_off + len); + debug_assert!(src.len() * BITS >= src_off + len); + + let mut dst_i = dst_off / BITS; + dst_off %= BITS; + let mut src_i = src_off / BITS; + src_off %= BITS; // copy dst prefix -> byte-align dst if dst_off > 0 { - let prefix = 8 - dst_off; - let mask = u8::MAX << dst_off; + let prefix = BITS - dst_off; + let mask = MAX << dst_off; dst[dst_i] &= !mask; dst[dst_i] |= (src[src_i] >> src_off) << dst_off; // exceeding a single byte of the src buffer - dst_off += 8 - src_off; + dst_off += BITS - src_off; src_off += prefix; - if let Some(reminder) = src_off.checked_sub(8) { + if let Some(reminder) = src_off.checked_sub(BITS) { src_i += 1; if reminder > 0 { dst[dst_i] |= src[src_i] << dst_off @@ -343,26 +353,28 @@ const fn copy_unaligned( // copy middle let mut i = 0; - while i < len / 8 { - dst[dst_i + i] = (src[src_i + i] >> src_off) | (src[src_i + i + 1] << (8 - src_off)); + while i < len / BITS { + dst[dst_i + i] = (src[src_i + i] >> src_off) | (src[src_i + i + 1] << (BITS - src_off)); i += 1; } // suffix - let suffix = len % 8; + let suffix = len % BITS; if suffix > 0 { - let last = len / 8; - let mask = u8::MAX >> (8 - suffix); + let last = len / BITS; + let mask = MAX >> (BITS - suffix); dst[dst_i + last] &= !mask; dst[dst_i + last] |= src[src_i + last] >> src_off; // exceeding a single byte of the src buffer - if suffix + src_off > 8 { - dst[dst_i + last] |= (src[src_i + last + 1] << (8 - src_off)) & mask; + if suffix + src_off > BITS { + dst[dst_i + last] |= (src[src_i + last + 1] << (BITS - src_off)) & mask; } } dst } + +/// The buffers have the same bit offsets #[inline(always)] const fn copy_aligned( mut dst: [u8; D], @@ -372,14 +384,17 @@ const fn copy_aligned( off: usize, mut len: usize, ) -> [u8; D] { - debug_assert!(off < 8); - debug_assert!(dst.len() * 8 >= dst_i * 8 + len); - debug_assert!(src.len() * 8 >= src_i * 8 + len); + const MAX: u8 = u8::MAX; + const BITS: usize = u8::BITS as _; + + debug_assert!(off < BITS); + debug_assert!(dst.len() * BITS >= dst_i * BITS + len); + debug_assert!(src.len() * BITS >= src_i * BITS + len); // copy prefix -> byte-align dst if off > 0 { - let prefix = 8 - (off % 8); - let mask = u8::MAX << (off % 8); + let prefix = BITS - (off % BITS); + let mask = MAX << (off % BITS); dst[dst_i] &= !mask; dst[dst_i] |= src[src_i] & mask; @@ -390,16 +405,16 @@ const fn copy_aligned( // copy middle let mut i = 0; - while i < len / 8 { + while i < len / BITS { dst[dst_i + i] = src[src_i + i]; i += 1; } // copy suffix - let suffix = len % 8; + let suffix = len % BITS; if suffix > 0 { - let last = len / 8; - let mask = u8::MAX >> (8 - suffix); + let last = len / BITS; + let mask = MAX >> (BITS - suffix); dst[dst_i + last] &= !mask; dst[dst_i + last] |= src[src_i + last]; }