diff --git a/Cargo.lock b/Cargo.lock index 1f1fbb80a0f..965d3f91f54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -146,6 +146,28 @@ dependencies = [ "shlex", ] +[[package]] +name = "bit-fields" +version = "0.1.0" +dependencies = [ + "bit-fields-macros", + "rand", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "bit-fields-macros" +version = "0.1.0" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rand", + "thiserror", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -270,6 +292,15 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "cpufeatures" version = "0.2.5" @@ -903,9 +934,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.47" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" dependencies = [ "unicode-ident", ] @@ -1096,18 +1127,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.147" +version = "1.0.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.147" +version = "1.0.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" dependencies = [ "proc-macro2", "quote", @@ -1116,9 +1147,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.89" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" +checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" dependencies = [ "itoa", "ryu", @@ -1150,9 +1181,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.103" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" dependencies = [ "proc-macro2", "quote", @@ -1221,9 +1252,15 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "unicode-ident" -version = "1.0.5" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" + +[[package]] +name = "unicode-segmentation" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" [[package]] name = "universal-hash" diff --git a/Cargo.toml b/Cargo.toml index 8c3056c9046..2d61a1114f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["src/firecracker", "src/jailer", "src/seccompiler", "src/rebase-snap"] +members = ["src/firecracker", "src/jailer", "src/seccompiler", "src/rebase-snap", "src/bit-fields"] default-members = ["src/firecracker"] [profile.dev] diff --git a/src/bit-fields-macros/Cargo.toml b/src/bit-fields-macros/Cargo.toml new file mode 100644 index 00000000000..2b3123cea7d --- /dev/null +++ b/src/bit-fields-macros/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "bit-fields-macros" +version = "0.1.0" +authors = ["Amazon Firecracker team "] +edition = "2021" +license = "Apache-2.0" + +[dependencies] +quote = "1.0.21" +proc-macro2 = "1.0.43" +thiserror = "1.0.32" +# For converting bit flag member names to constant names. +convert_case = "0.6.0" + +[dev-dependencies] +rand = "0.8.5" + +[lib] +proc-macro = true \ No newline at end of file diff --git a/src/bit-fields-macros/src/builder.rs b/src/bit-fields-macros/src/builder.rs new file mode 100644 index 00000000000..122cd39d1ab --- /dev/null +++ b/src/bit-fields-macros/src/builder.rs @@ -0,0 +1,726 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use convert_case::{Case, Casing}; +use proc_macro2::{Ident, TokenStream}; +use quote::quote; + +use crate::utils::{DataTypeToken, MultiLineString}; +use crate::{BitFlag, BitRange, Member}; + +/// Builder for bit fields. +#[allow(clippy::module_name_repetitions)] +#[derive(Debug)] +pub struct BitFieldBuilder { + /// String used to define `From>`. + flag_matching_from_hashset: TokenStream, + /// String used to define `From>`. + flag_setting_hashset: TokenStream, + /// String used to define `From>`. + field_matching_from_hashmap: TokenStream, + /// String used to define `From>`. + field_setting_hashmap: TokenStream, + /// String used to define the table used in the rustdoc for the bit field. + struct_doc_table_layout: Vec, + /// Accessor methods to members. + struct_accessors: TokenStream, + /// String used to form the display the bit field, the lines represent: + /// 1. Top border + /// 2. Bit numbers + /// 3. Border + /// 4. Member idents + /// 5. Border + /// 6. Member values + /// 7. Bottom border + /// Fmt values (since write doesn't work with in place ones) + display_string: MultiLineString, + /// String used to pass arguments for `std::fmt::Display` implementation. + display_fmt_string: TokenStream, + /// Rustdoc to attach to generated bit field. + rustdoc: String, + /// Struct data type (e.g. `u8`) + data_type: DataTypeToken, + /// Struct identifier + struct_name: Ident, + /// Bit flag constants to construct a field with a single flag active. + flag_constants: TokenStream, + /// Bit mask including all bit flags. + bit_flag_mask: TokenStream, + /// Bit mask including all bit ranges. + bit_range_mask: TokenStream, +} + +impl BitFieldBuilder { + /// Constructs new `BitFieldBuilder`. + pub fn new(rustdoc: String, struct_name: Ident, data_type: DataTypeToken) -> Self { + Self { + flag_matching_from_hashset: TokenStream::new(), + flag_setting_hashset: TokenStream::new(), + field_matching_from_hashmap: TokenStream::new(), + field_setting_hashmap: TokenStream::new(), + struct_doc_table_layout: vec![quote! { + #[doc = "Bit/sIdentifierDescription"] + }], + struct_accessors: TokenStream::new(), + #[rustfmt::skip] + display_string: MultiLineString::from("\ + ┌───────\n\ + │ \x1b[1mBit/s\x1b[0m \n\ + ├───────\n\ + │ \x1b[1mDesc\x1b[0m \n\ + ├───────\n\ + │ \x1b[1mValue\x1b[0m \n\ + └───────", + ), + display_fmt_string: TokenStream::new(), + rustdoc, + data_type, + struct_name, + flag_constants: TokenStream::new(), + bit_flag_mask: quote! { 0 }, + bit_range_mask: quote! { 0 }, + } + } + + /// Adds a bit member to the structure. + pub(crate) fn add(self, member: Member) -> Self { + match member { + Member::BitRange(field) => self.add_bit_range(field), + Member::BitFlag(flag) => self.add_bit_flag(flag), + } + } + + /// Adds a bit range to the structure. + pub(crate) fn add_bit_range( + mut self, + BitRange { + range, + rustdoc, + identifier, + skip, + }: BitRange, + ) -> Self { + let identifier_str = identifier.to_string(); + let data_type = &self.data_type; + let (start, stop) = (range.start, range.end); + + // We check if the rustdoc string is empty to avoid adding an empty doc comment + // (e.g. `#[doc=""]`) which would suppress lint warnings. + let rustdoc_stream = if rustdoc.is_empty() { + TokenStream::new() + } else { + quote! { #[doc=#rustdoc] } + }; + + // Display + // ------------------------ + // Use first 10 characters of identifier_str. + let cropped = identifier_str.chars().take(10).collect::(); + #[rustfmt::skip] + self.display_string.push_str(&format!("\ + ┬─────────────\n\ + │\x20 {:02}..{:02} \n\ + ┼─────────────\n\ + │\x20 {:>10} \n\ + ┼─────────────\n\ + │\x20{{:>11}} \n\ + ┴─────────────\ + ", + start, + stop, + cropped, + )); + self.display_fmt_string.extend(quote! { + self.#identifier().to_string(), + }); + + // Struct member + // ------------------------ + let ident_mut = quote::format_ident!("{}_mut", identifier); + self.struct_accessors.extend(quote! { + #rustdoc_stream + pub fn #identifier(&self) -> bit_fields::BitRange<#data_type,#start,#stop> { + bit_fields::BitRange(&self.0) + } + #rustdoc_stream + pub fn #ident_mut(&mut self) -> bit_fields::BitRangeMut<#data_type,#start,#stop> { + bit_fields::BitRangeMut(&mut self.0) + } + }); + + let base = data_type.base(); + + // Bit range mask + // ------------------------ + self.bit_range_mask + .extend(quote! { | bit_fields::BitRange::<#base,#start,#stop>::MASK }); + + // try_from_field_map + // ------------------------ + if !skip { + self.field_matching_from_hashmap.extend(quote! { + #identifier_str => { + match bit_fields::BitRangeMut::<#base,#start,#stop>(&mut acc).checked_assign(#base::from(v)) { + Ok(_) => Ok(acc), + Err(err) => Err(Self::Error::CheckedAssign(err)) + } + }, + }); + self.field_setting_hashmap.extend(quote! { + map.insert(T::from(#identifier_str),#base::from(&bit_field.#identifier())); + }); + } + + // Struct rustdoc table + // ------------------------ + let rustdoc_string = format!( + "{:02}..{:02}{}{}", + start, + // Due to the earlier check on `stop <= start` we can guarantee + // `stop > start >= 0`, thus `stop >= 1` thus `stop - 1 >=0` thus this + // will never panic. + stop, + identifier_str, + rustdoc + ); + self.struct_doc_table_layout.push(quote! { + #[doc=#rustdoc_string] + }); + + self + } + /// Adds a bit flag to the structure. + pub(crate) fn add_bit_flag( + mut self, + BitFlag { + index, + rustdoc, + identifier, + skip, + }: BitFlag, + ) -> Self { + let identifier_str = identifier.to_string(); + let data_type = &self.data_type; + + // We check if the rustdoc string is empty to avoid adding an empty doc comment + // (e.g. `#[doc=""]`) which would suppress lint warnings. + let rustdoc_stream = if rustdoc.is_empty() { + TokenStream::new() + } else { + quote! { #[doc=#rustdoc] } + }; + + // Display + // ------------------------ + // Use first 4 characters of the identifier_str. + let cropped = identifier_str.chars().take(4).collect::(); + #[rustfmt::skip] + self.display_string.push_str(&format!("\ + ┬───────\n\ + │\x20 {:02} \n\ + ┼───────\n\ + │\x20{:>5} \n\ + ┼───────\n\ + │\x20{{:>5}} \n\ + ┴───────\ + ", + index,cropped + )); + self.display_fmt_string.extend(quote! { + self.#identifier().to_string(), + }); + // Struct member + // ------------------------ + let ident_mut = quote::format_ident!("{}_mut", identifier); + self.struct_accessors.extend(quote! { + #rustdoc_stream + pub fn #identifier(&self) -> bit_fields::Bit<#data_type,#index> { + bit_fields::Bit(&self.0) + } + #rustdoc_stream + pub fn #ident_mut(&mut self) -> bit_fields::BitMut<#data_type,#index> { + bit_fields::BitMut(&mut self.0) + } + }); + + // Flag constant + // ------------------------ + let identifier_str_upper_snake = identifier_str.to_case(Case::UpperSnake); + // If the given member is already snake case, we don't define a constant. + if identifier_str_upper_snake != identifier_str { + let ident_upper_snake = quote::format_ident!("{}", identifier_str_upper_snake); + self.flag_constants.extend(quote! { + #rustdoc_stream + pub const #ident_upper_snake: Self = Self(1 << (#index as #data_type)); + }); + } + + // Struct rustdoc table + // ------------------------ + let rustdoc_string = format!( + "{:02}{}{}", + index, identifier_str, rustdoc + ); + self.struct_doc_table_layout.push(quote! { + #[doc=#rustdoc_string] + }); + + let base = data_type.base(); + + // Bit flag mask + // ------------------------ + self.bit_flag_mask + .extend(quote! { | bit_fields::Bit::<#base,#index>::MASK }); + + // try_from_flag_set + // ------------------------ + if !skip { + self.flag_matching_from_hashset.extend(quote! { + #identifier_str => Ok(acc | bit_fields::Bit::<#base,#index>::MASK), + }); + self.flag_setting_hashset.extend(quote! { + if bit_field.#identifier().is_on() { + set.insert(T::from(#identifier_str)); + } + }); + } + + self + } + + /// Ends the bit field, completing the display string. + fn end(&mut self) { + #[rustfmt::skip] + self.display_string.push_str("\ + ┐\n\ + │\n\ + ┤\n\ + │\n\ + ┤\n\ + │\n\ + ┘\ + "); + } + + /// Composes `self` into `proc_macro::TokenStream`. + // When `quote!` is used on an iterable it always prouces this warnings. + #[allow(clippy::too_many_lines, clippy::shadow_reuse)] + pub fn compose(mut self) -> TokenStream { + self.end(); + + // De-structure self into values we can pass to `quote!`. + let BitFieldBuilder { + flag_matching_from_hashset, + flag_setting_hashset, + field_matching_from_hashmap, + field_setting_hashmap, + struct_doc_table_layout, + struct_accessors, + display_string, + display_fmt_string, + rustdoc, + data_type, + struct_name, + flag_constants, + bit_flag_mask, + bit_range_mask, + } = self; + + let serde = quote! { , serde::Serialize, serde::Deserialize }; + + let set_theory = quote! { + /// Returns `true` if `self` is a [superset](https://en.wikipedia.org/wiki/Subset) of `other`. + /// + /// This only considers defined bit flags. + #[allow(clippy::bad_bit_mask)] + pub fn superset(&self, other: &Self) -> bool { + (self.0 & other.0 & Self::BIT_FLAG_MASK) == (other.0 & Self::BIT_FLAG_MASK) + } + /// Returns `true` if `self` is a [subset](https://en.wikipedia.org/wiki/Subset) of `other`. + /// + /// This only considers defined bit flags. + #[allow(clippy::bad_bit_mask)] + pub fn subset(&self, other: &Self) -> bool { + (self.0 & other.0 & Self::BIT_FLAG_MASK) == (self.0 & Self::BIT_FLAG_MASK) + } + /// Returns `true` if `self` and `other` are [disjoint sets](https://en.wikipedia.org/wiki/Disjoint_sets). + /// + /// This only considers defined bit flags. + #[allow(clippy::bad_bit_mask)] + pub fn disjoint(&self, other: &Self) -> bool { + (self.0 & other.0 & Self::BIT_FLAG_MASK) == 0 + } + /// Returns the [`intersection`](https://en.wikipedia.org/wiki/Intersection_(set_theory)) of `self` and `other`. + /// + /// Returns the intersection of defined bit flags, all other bits will be 0. + #[allow(clippy::bad_bit_mask)] + pub fn intersection(&self, other: &Self) -> Self { + Self(self.0 & other.0 & Self::BIT_FLAG_MASK) + } + /// Returns the [`union`](https://en.wikipedia.org/wiki/Union_(set_theory)) of `self` and `other`. + /// + /// Returns the union of defined bit flags, all other bits will be 0. + #[allow(clippy::bad_bit_mask)] + pub fn union(&self, other: &Self) -> Self { + Self((self.0 | other.0) & Self::BIT_FLAG_MASK) + } + }; + + let bit_index = (0..self.data_type.size()) + .map(|i| { + quote! { + impl bit_fields::BitIndex<#data_type,#i> for #struct_name { + fn bit(&self) -> bit_fields::Bit<#data_type,#i> { + bit_fields::Bit(&self.0) + } + } + impl bit_fields::BitIndexMut<#data_type,#i> for #struct_name { + fn bit_mut(&mut self) -> bit_fields::BitMut<#data_type,#i> { + bit_fields::BitMut(&mut self.0) + } + } + } + }) + .collect::(); + + let index_fn = quote! { + /// Returns a reference to the `N`th bit. + #[allow(clippy::same_name_method)] + pub fn bit(&self) -> bit_fields::Bit<#data_type,N> + where + Self: bit_fields::BitIndex<#data_type,N>, + { + >::bit(self) + } + /// Returns a mutable reference to the `N`th bit. + #[allow(clippy::same_name_method)] + pub fn bit_mut(&mut self) -> bit_fields::BitMut<#data_type,N> + where + Self: bit_fields::BitIndexMut<#data_type,N>, + { + >::bit_mut(self) + } + }; + + let try_from_flag_set = quote! { + impl std::convert::TryFrom> for #struct_name { + type Error = bit_fields::TryFromFlagSetError; + fn try_from(set: std::collections::HashSet) -> Result { + let value = set.into_iter().try_fold(0,|mut acc,key| match key.to_string().as_str() { + #flag_matching_from_hashset + _ => Err(bit_fields::TryFromFlagSetError) + })?; + Ok(Self(value)) + } + } + }; + + let base = data_type.base(); + + // - Constructing a bit field from a map of fields + // - Constructing a map of fields from a reference to the bit field + let try_from_field_map = quote! { + impl std::convert::TryFrom> for #struct_name where #base: From { + type Error = bit_fields::TryFromFieldMapError; + fn try_from(map: std::collections::HashMap) -> Result { + let value = map.into_iter().try_fold(0,|mut acc,(k,v)|match k.to_string().as_str() { + #field_matching_from_hashmap + _ => Err(bit_fields::TryFromFieldMapError::UnknownRange) + })?; + Ok(Self(value)) + } + } + }; + + let try_from_flag_set_and_field_map = quote! { + impl std::convert::TryFrom<(std::collections::HashSet,std::collections::HashMap)> for #struct_name where #base: From { + type Error = bit_fields::TryFromFlagSetAndFieldMapError; + fn try_from((set,map):(std::collections::HashSet,std::collections::HashMap)) -> Result { + let value = set.into_iter().try_fold(0,|mut acc,key| match key.to_string().as_str() { + #flag_matching_from_hashset + _ => Err(bit_fields::TryFromFlagSetAndFieldMapError::MissingFlag) + })?; + + let value = map.into_iter().try_fold(value,|mut acc,(k,v)|match k.to_string().as_str() { + #field_matching_from_hashmap + _ => Err(bit_fields::TryFromFlagSetAndFieldMapError::UnknownRange) + })?; + + Ok(Self(value)) + } + } + }; + + let binary_ops = quote! { + impl std::ops::BitAnd for #struct_name { + type Output = Self; + fn bitand(self, rhs: Self) -> Self::Output { + Self(self.0 & rhs.0) + } + } + impl std::ops::Not for #struct_name { + type Output = Self; + fn not(self) -> Self::Output { + Self(!self.0) + } + } + }; + + let equal_cmp = quote! { (self.0 & mask) == (other.0 & mask) }; + + let display_impl = { + let display_full_string = display_string.to_string(); + quote! { + impl std::fmt::Display for #struct_name { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f,#display_full_string,#display_fmt_string) + } + } + } + }; + + let header = format!( + "A {} bit structure containing a number of bit flags and bit fields.", + self.data_type.size() + ); + + let empty = quote! { + /// Alias for `Self(0)`. + pub const fn empty() -> Self { + Self(0) + } + }; + let base_type = data_type.base(); + + quote! { + #[doc=#rustdoc] + /// + /// --- + /// + #[doc=#header] + /// + /// Implemented operations such as [`std::cmp::Eq`], [`std::ops::BitOr`] and + /// [`std::ops::Not`] apply to the underlying integer type. + /// + /// ## Layout + /// + /// + #(#struct_doc_table_layout)* + ///
+ #[allow(clippy::unsafe_derive_deserialize)] + #[derive(Debug, Clone, Copy, Eq, PartialEq #serde)] + #[repr(C)] + pub struct #struct_name(pub #data_type); + + impl std::cmp::PartialEq<#data_type> for #struct_name { + fn eq(&self, other: &#data_type) -> bool { + self.0 == *other + } + } + impl std::fmt::Binary for #struct_name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Binary::fmt(&self.0, f) + } + } + impl std::fmt::LowerHex for #struct_name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::LowerHex::fmt(&self.0, f) + } + } + impl std::fmt::Octal for #struct_name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Octal::fmt(&self.0, f) + } + } + /// Constructs `self` from the data type. + impl std::convert::From<#data_type> for #struct_name { + fn from(data: #data_type) -> Self { + Self(data) + } + } + /// Constructs the data type from `self`. + impl std::convert::From<#struct_name> for #data_type { + fn from(bit_field: #struct_name) -> Self { + bit_field.0 + } + } + impl std::ops::BitOr for #struct_name { + type Output = Self; + fn bitor(self, rhs: Self) -> Self::Output { + Self(self.0 | rhs.0) + } + } + impl bit_fields::Equal for #struct_name { + /// Compares whether `self` is equal to `other` ignoring undefined bits. + fn equal(&self, other: &Self) -> bool { + let mask = Self::BIT_FLAG_MASK | Self::BIT_RANGE_MASK; + #equal_cmp + } + } + impl #struct_name { + /// Mask for all bit flags. + const BIT_FLAG_MASK: #base_type = #bit_flag_mask; + /// Mask for all bit fields. + const BIT_RANGE_MASK: #base_type = #bit_range_mask; + /// Returns [`std::cmp::Ordering`] based on bit flags. + /// - `Some(Ordering::Equal)` - Bit flags match between `self` and `other`. + /// - `Some(Ordering::Greater)` - Bit flags of `self` are a strict superset of bit flags of `other`. + /// - `Some(Ordering::Less)` - Bit flags of `self` are a strict subset of bit flags of `other`. + /// - `None` - None of the above conditions are met. + pub fn cmp_flags(&self,other: &Self) -> Option { + if ::equal(self,other) { + Some(std::cmp::Ordering::Equal) + } + else if self.superset(other) { + Some(std::cmp::Ordering::Greater) + } + else if self.subset(other) { + Some(std::cmp::Ordering::Less) + } + else { + None + } + } + #empty + #struct_accessors + #set_theory + #index_fn + #flag_constants + } + #display_impl + #try_from_flag_set + impl + std::cmp::Eq + std::hash::Hash> std::convert::From<&#struct_name> for std::collections::HashSet { + fn from(bit_field: &#struct_name) -> Self { + let mut set = Self::new(); + #flag_setting_hashset + set + } + } + #try_from_field_map + impl + std::cmp::Eq + std::hash::Hash> std::convert::From<&#struct_name> for std::collections::HashMap { + fn from(bit_field: &#struct_name) -> Self { + let mut map = Self::new(); + #field_setting_hashmap + map + } + } + #try_from_flag_set_and_field_map + impl + std::cmp::Eq + std::hash::Hash> std::convert::From<&#struct_name> for (std::collections::HashSet,std::collections::HashMap) { + fn from(bit_field: &#struct_name) -> Self { + ( + bit_field.into(), + bit_field.into() + ) + } + } + #binary_ops + #bit_index + } + } +} + +#[cfg(test)] +mod tests { + #![allow(clippy::arithmetic_side_effects, clippy::integer_arithmetic)] + use std::ops::Range; + + use proc_macro2::{Ident, Span}; + use rand::Rng; + + use super::*; + use crate::{BitFlag, BitRange}; + + const COMPOSE_FUZZ_LIMIT: usize = 10; + const ADD_FUZZ_LIMIT: usize = 100; + const RAND_STR_LEN: usize = 100; + + // Construct an ident with a given string. + fn ident(s: &str) -> Ident { + Ident::new(s, Span::call_site()) + } + + // Construct a pseudo-random ident. + fn rand_ident(rng: &mut R) -> Ident { + Ident::new(&rand_string(rng), Span::call_site()) + } + + // Construct a pseudo-random string. + #[allow(clippy::as_conversions)] + fn rand_string(rng: &mut R) -> String { + (0..RAND_STR_LEN) + .map(|_| (rng.gen_range(0..26u8) + 65) as char) + .collect() + } + + // Construct a pseudo-random `BitFieldBuilder`. + fn rand_builder(rng: &mut R, len: usize) -> BitFieldBuilder { + let iter = (0..len).map(|_| (rng.gen(), rand_string(rng), rand_ident(rng), rng.gen())); + iter.fold(BitFieldBuilder::default(), |builder, item| { + let (start, rustdoc, identifier, end_opt) = item; + if let Some(end) = end_opt { + builder.add_bit_range(BitRange { + range: Range { start, end }, + rustdoc, + identifier, + skip: false, + }) + } else { + builder.add_bit_flag(BitFlag { + index: start, + rustdoc, + identifier, + skip: false, + }) + } + }) + } + + // Construct a default `BitFieldBuilder` + impl std::default::Default for BitFieldBuilder { + fn default() -> Self { + Self::new( + String::from("Some basic rustdoc"), + ident("DefaultBuilder"), + DataTypeToken::default(), + ) + } + } + + #[test] + fn builder_default() { + let _builder = BitFieldBuilder::default(); + } + #[test] + fn builder_add_bit_range() { + let builder = BitFieldBuilder::default(); + builder.add_bit_range(BitRange { + range: Range { start: 0, end: 1 }, + rustdoc: String::from("one rustdoc"), + identifier: ident("one"), + skip: false, + }); + } + #[test] + fn builder_add_bit_flag() { + let builder = BitFieldBuilder::default(); + builder.add_bit_flag(BitFlag { + index: 0, + rustdoc: String::from("one rustdoc"), + identifier: ident("one"), + skip: false, + }); + } + #[test] + fn builder_add_fuzz() { + let mut rng = rand::thread_rng(); + let _builder = rand_builder(&mut rng, ADD_FUZZ_LIMIT); + } + #[test] + fn builder_compose_fuzz() { + let mut rng = rand::thread_rng(); + for _ in 0..COMPOSE_FUZZ_LIMIT { + let builder = rand_builder(&mut rng, COMPOSE_FUZZ_LIMIT); + let _token_stream = builder.compose(); + } + } +} diff --git a/src/bit-fields-macros/src/lib.rs b/src/bit-fields-macros/src/lib.rs new file mode 100644 index 00000000000..a7ee40ff741 --- /dev/null +++ b/src/bit-fields-macros/src/lib.rs @@ -0,0 +1,276 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +#![warn(clippy::pedantic, clippy::restriction)] +#![allow( + clippy::blanket_clippy_restriction_lints, + clippy::implicit_return, + clippy::pattern_type_mismatch, + clippy::std_instead_of_alloc, + clippy::std_instead_of_core, + clippy::pub_use, + clippy::non_ascii_literal, + clippy::single_char_lifetime_names, + clippy::exhaustive_enums, + clippy::exhaustive_structs, + clippy::unseparated_literal_suffix, + clippy::mod_module_files +)] + +//! Procedural macros for the `bit-fields` crate. + +use std::convert::TryFrom; + +use proc_macro2::{Delimiter, Ident, Span, TokenTree}; + +/// Utility functions. +mod utils; +use utils::DataTypeToken; + +/// Builder struct. +mod builder; +use builder::BitFieldBuilder; + +/// Parser struct. +mod parser; + +/// Convenience macro for errors in [`bitfield`]. +macro_rules! span_error { + ($span: expr, $msg: expr) => {{ + let x = $msg; + return (quote::quote_spanned! { + $span => #x + }) + .into(); + }}; +} + +/// Convenience macro for errors in [`bitfield`]. +macro_rules! token_error { + ($token: expr, $msg: expr) => { + span_error!($token.span(), $msg) + }; +} + +/// Convenience macro for errors in [`bitfield`]. +macro_rules! callsite_error { + ($msg:expr) => { + span_error!(Span::call_site(), $msg) + }; +} + +/// Convenience macro for errors in [`bitfield`]. +macro_rules! unwrap_or_emit { + ($res:expr) => { + match $res { + Ok(x) => x, + Err((span, err)) => span_error!(span, err.to_string()), + } + }; +} + +/// Bit struct member +#[derive(Debug)] +pub(crate) enum Member { + /// Bit field + BitRange(BitRange), + /// Bit flag + BitFlag(BitFlag), +} +/// Bit field. +#[derive(Debug)] +pub(crate) struct BitRange { + /// Inclusive start of bit field. + range: std::ops::Range, + /// Rustdoc comment. + rustdoc: String, + /// Member identifier. + identifier: Ident, + /// Skip serialization/deserialization and `From` attribute. + skip: bool, +} +/// Bit flag. +#[derive(Debug)] +pub(crate) struct BitFlag { + /// Index of bit flag. + index: u8, + /// Rustdoc comment. + rustdoc: String, + /// Member identifier. + identifier: Ident, + /// Skip serialization/deserialization and `From` attribute. + skip: bool, +} + +/// Procedural macro error type. +type ProcError = (Span, T); + +/// Procedural macro to generate bit fields. +/// +/// ```ignore +/// use std::mem::size_of; +/// #[rustfmt::skip] +/// bit_fields::bitfield!(GeneratedBitField, u32, { +/// RANGE1: 0..1, +/// SSE: 2, +/// SSE1: 3, +/// RANGE2: 4..6, +/// SSE2: 9, +/// SSE3: 10, +/// RANGE3: 12..15, +/// SSE4: 17 +/// }); +/// assert_eq!(size_of::(), size_of::()); +/// let bitfield = GeneratedBitField::from(23548); +/// println!("{}", bitfield); +/// ``` +/// Prints: +/// ```test +/// ┌───────┬────────────┬───────┬───────┬────────────┬───────┬───────┬────────────┬───────┐ +/// │ Bit/s │ 00..01 │ 02 │ 03 │ 04..06 │ 09 │ 10 │ 12..15 │ 17 │ +/// ├───────┼────────────┼───────┼───────┼────────────┼───────┼───────┼────────────┼───────┤ +/// │ Desc │ RANGE1 │ SSE │ SSE1 │ RANGE2 │ SSE2 │ SSE3 │ RANGE3 │ SSE4 │ +/// ├───────┼────────────┼───────┼───────┼────────────┼───────┼───────┼────────────┼───────┤ +/// │ Value │ 0 │ true │ true │ 3 │ true │ false │ 5 │ false │ +/// └───────┴────────────┴───────┴───────┴────────────┴───────┴───────┴────────────┴───────┘ +/// ``` +/// +/// **Important**: Undefined bits are not preserved on serialization and deserialization. +/// +/// # Panics +/// +/// When failing to parse values to token streams. This should never occur. +#[proc_macro] +pub fn bitfield(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + /// Errors returned on malformed or missing rustdoc comment. + const RUSTDOC_ERR: &str = "Expected rustdoc comment"; + /// Description of correct bit field ident. + const IDENT_ERR: &str = "Expected struct identifier or rustdoc comment"; + + /// Separator between struct identifiers and struct data type. + const IDENT_TYPE_SEPARATOR_ERR: &str = "Expected punctuation comma (',')"; + + /// Description of correct bit field type. + const TYPE_ERR: &str = "Expected type identifier, options: [u8, u16, u32, u64, u128]"; + + /// Separator between struct data type and struct members. + const TYPE_FIELDS_SEPARATOR_ERR: &str = "Expected punctuation comma (',')"; + + /// Description of correct bit field array. + const FIELDS_ERR: &str = "Expected a brace delimited group (`{ ... }`) of identifiers and bit \ + indexes. The bit indexes must be within the bounds of the given \ + data type. The identifiers must be unique."; + + let item = proc_macro2::TokenStream::from(input); + let mut token_stream_iter = item.into_iter(); + + // Get struct identifier (e.g. `MyBitField`) and any preceding rustdoc comments. + let (rustdoc, struct_name) = { + let mut rustdoc = String::new(); + let struct_name = loop { + match token_stream_iter.next() { + // Rustdoc case + Some(TokenTree::Punct(punct)) if punct.as_char() == '#' => { + match token_stream_iter.next() { + Some(TokenTree::Group(group)) + if group.delimiter() == Delimiter::Bracket => + { + let group_vec = group.stream().into_iter().collect::>(); + #[allow(clippy::cmp_owned)] + match group_vec.as_slice() { + [TokenTree::Ident(group_ident), TokenTree::Punct(group_punct), TokenTree::Literal(group_lit)] + if group_ident.to_string() == "doc" + && group_punct.as_char() == '=' => + { + // Check for then remove " from start and end of string. + let comment_unenclosed = { + let group_string = group_lit.to_string(); + let mut chars = group_string.chars(); + if let (Some('"'), Some('"')) = + (chars.next(), chars.next_back()) + { + String::from(chars.as_str()) + } else { + token_error!( + group_lit, + "Rustdoc comment missing enclosing \" characters." + ); + } + }; + // Trim space leading spaces. + // E.g. A coment like `/// abcde` will become `" abcde"` and we + // want `abcde`. + let comment_trimmed = comment_unenclosed.trim_start(); + // We append to the rustdoc string. + rustdoc.push_str(comment_trimmed); + rustdoc.push(' '); + } + _ => token_error!(group, RUSTDOC_ERR), + } + } + Some(token) => token_error!(token, RUSTDOC_ERR), + None => callsite_error!(RUSTDOC_ERR), + } + } + // Ident case + Some(TokenTree::Ident(ident)) => break ident, + Some(token) => token_error!(token, IDENT_ERR), + None => callsite_error!(IDENT_ERR), + } + }; + (rustdoc, struct_name) + }; + + // Check struct identifier and data type identifier are separated by ','. + match token_stream_iter.next() { + Some(TokenTree::Punct(punct)) if punct.as_char() == ',' => (), + Some(token_tree) => token_error!(token_tree, IDENT_TYPE_SEPARATOR_ERR), + None => callsite_error!(IDENT_TYPE_SEPARATOR_ERR), + } + // Get struct data type identifier token. + let data_type_ident = match token_stream_iter.next() { + Some(TokenTree::Ident(ident)) => ident, + Some(token) => token_error!(token, TYPE_ERR), + None => callsite_error!(TYPE_ERR), + }; + // Get struct data type as number of bits and check it is valid when doing this (valid + // identifiers are `u8`, `u16`, `u32`, `u64` or `u128`). + let data_type = unwrap_or_emit!(DataTypeToken::try_from(data_type_ident)); + + // Check data type identifier and field definitions are separated by ','. + match token_stream_iter.next() { + Some(TokenTree::Punct(punct)) if punct.as_char() == ',' => (), + Some(TokenTree::Punct(punct)) => token_error!(punct, TYPE_FIELDS_SEPARATOR_ERR), + Some(token_tree) => token_error!(token_tree, TYPE_FIELDS_SEPARATOR_ERR), + None => callsite_error!(TYPE_FIELDS_SEPARATOR_ERR), + } + + // Get fields group + let group = match token_stream_iter.next() { + Some(TokenTree::Group(group)) if group.delimiter() == Delimiter::Brace => group, + Some(TokenTree::Group(group)) => token_error!( + group, + format!( + "Found group delimiter `{:?}` expected group delimiter `Brace` (`{{ ... }}`)", + group.delimiter() + ) + ), + Some(token_tree) => token_error!(token_tree, FIELDS_ERR), + None => callsite_error!(FIELDS_ERR), + }; + + // Parses token stream into bit field member iterator. + let member_tokens = group.stream().into_iter().collect::>(); + let member_iter = parser::BitFieldMembersParser::from((data_type.size(), member_tokens.iter())); + + // Builds field struct. + let builder = unwrap_or_emit!(member_iter.into_iter().try_fold( + BitFieldBuilder::new(rustdoc, struct_name, data_type), + |builder, member_res| { + let member = member_res?; + Ok(builder.add(member)) + } + )); + + proc_macro::TokenStream::from(builder.compose()) +} diff --git a/src/bit-fields-macros/src/parser.rs b/src/bit-fields-macros/src/parser.rs new file mode 100644 index 00000000000..090bebf6599 --- /dev/null +++ b/src/bit-fields-macros/src/parser.rs @@ -0,0 +1,946 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +use std::collections::HashMap; +use std::ops::Range; + +use proc_macro2::{Delimiter, Ident, Literal, Span, TokenTree}; + +use crate::{BitFlag, BitRange, Member}; + +/// Parses a slice of tokens into an iterator of bit field members. +pub(crate) struct BitFieldMembersParser<'a> { + /// Token slice iterator. + iter: std::slice::Iter<'a, TokenTree>, + /// Hashmap of used identifiers, where `None` represents a bit flag and `Some(_)` represents a + /// bit range. + existing: HashMap>>, + /// Rustdoc. + rustdoc: String, + /// Number of bits in data type, e.g. `size_of::() * 8`. + size: u8, + /// Error flag we set when encountering an error. + error: bool, + /// Skip attribute flag, indicating the next member should be skipped on serialization and + /// deserialization. + skip: bool, + /// Members which are present in the conversion to/from sets of flags and maps of fields cannot + /// have overlapping bits as this leads to undefined behavior in as the value of the bit field + /// would depend on the ordering of keys in a `HashMap` and/or `HashSet` (which is + /// inconsistent). Thus this case would enable the same data to produce to bit fields of + /// different values. + non_skipped_bits: Vec, +} +impl<'a> From<(u8, std::slice::Iter<'a, TokenTree>)> for BitFieldMembersParser<'a> { + fn from((size, iter): (u8, std::slice::Iter<'a, TokenTree>)) -> Self { + Self { + iter, + existing: HashMap::new(), + rustdoc: String::new(), + size, + error: false, + skip: false, + non_skipped_bits: vec![false; usize::from(size)], + } + } +} + +/// Error type for [` as std::iter::Iterator>::next`]. +#[derive(Debug, thiserror::Error)] +pub(crate) enum BitFieldMembersParserIterError { + /// Identifier already used. + #[error("Identifier already used.")] + DuplicateIdentifier, + /// Failed to get field index. + #[error("Failed to get field indices: {0}")] + IndexField(IndexFieldError), + /// Failed to get flag index. + #[error("Failed to get flag index: {0}")] + IndexFlag(IndexFlagError), + /// Unknown attribute supplied on member. + #[error( + "Unknown attribute supplied on member. Supported attributes are `#[skip]` and \ + `#[doc=\"..\"]`." + )] + UnknownAttribute, + /// Rustdoc comment missing enclosing " characters. + #[error("Rustdoc comment missing enclosing \" characters.")] + RustdocUnenclosed, + /// Badly defined members. + #[error("Badly defined members: {0:?}.")] + BadlyDefinedMembers(Vec), + /// Expected commma + #[error("Expected commma.")] + ExpectedComma, + /// Cannot define member as indexing into bit range, as bit range does not exist. + #[error( + "Cannot define member on bit range, as bit range does not exist. Bit ranges need to be \ + defined before members which reference them." + )] + IndexingMissingRange, + /// Cannt define member as indexing into bit flag, as you cannot index a bit flag. + #[error("Cannt define member as indexing into bit flag, as you cannot index a bit flag.")] + IndexingBitFlag, + /// Malformed bit range index. + #[error("Malformed bit range index.")] + IndexingMalformed, + /// Members which are present in the conversion to/from sets of flags and maps of fields cannot + /// have overlapping bits as this leads to undefined behavior in as the value of the bit field + /// would depend on the ordering of keys in a `HashMap` and/or `HashSet` (which is + /// inconsistent). Thus this case would enable the same data to produce to bit fields of + /// different values. + #[error( + "Members which are present in the conversion to/from sets of flags and maps of fields \ + cannot have overlapping bits as this leads to undefined behavior in as the value of the \ + bit field would depend on the ordering of keys in a `HashMap` and/or `HashSet` (which is \ + inconsistent). Thus this case would enable the same data to produce to bit fields of \ + different values." + )] + OverlappingNonSkipped, +} + +/// Advantces parser iterator expecting `base+1`th token to be `None` or a comma. +fn advance_iter<'a>( + iter: &mut std::slice::Iter<'a, TokenTree>, + tail: &[TokenTree], + base: usize, +) -> Result<(), crate::ProcError> { + // Move past this identified slice. + iter.nth(base); + // Match comma + #[allow(clippy::pattern_type_mismatch)] + match tail.first() { + // If no tailing comma, we also we expect this to be end + None => Ok(()), + // If tailing comma + Some(TokenTree::Punct(comma)) if comma.as_char() == ',' => { + // Advance over comma + iter.next(); + Ok(()) + } + // Else if not end and next token is not comma + Some(token) => Err((token.span(), BitFieldMembersParserIterError::ExpectedComma)), + } +} + +impl std::iter::Iterator for BitFieldMembersParser<'_> { + type Item = Result>; + #[allow(clippy::too_many_lines)] + fn next(&mut self) -> Option { + // After the iterator yields `Some(Err(_))` it always yields `None`. + if self.error { + return None; + } + // This loop is required to collect rustdoc comments + loop { + #[allow(clippy::pattern_type_mismatch, clippy::cmp_owned)] + match self.iter.as_slice() { + // Bit reference range: [ident, punct, ident, group [ literal, punct, punct, literal + // ], punct, ..] e.g. "see2: sse[0..2]," + [TokenTree::Ident(ident), TokenTree::Punct(colon), TokenTree::Ident(parent_bit_range), TokenTree::Group(group), end @ ..] + if colon.as_char() == ':' && group.delimiter() == Delimiter::Bracket => + { + // Move past this identified slice. + if let Err(err) = advance_iter(&mut self.iter, end, 3) { + self.error = true; + return Some(Err(err)); + } + + let vec = group.stream().into_iter().collect::>(); + // Checks if parent key exists in the map + let parent_bit_range_range = + match self.existing.get(&parent_bit_range.to_string()) { + Some(Some(some)) => some.clone(), + Some(None) => { + self.error = true; + return Some(Err(( + group.span(), + BitFieldMembersParserIterError::IndexingBitFlag, + ))); + } + None => { + self.error = true; + return Some(Err(( + group.span(), + BitFieldMembersParserIterError::IndexingMissingRange, + ))); + } + }; + + return match vec.as_slice() { + // Bit flag: [literal] + [TokenTree::Literal(index_token)] => { + // Check bit index + match check_bit_flag( + &mut self.existing, + ident, + index_token, + parent_bit_range_range.clone(), + parent_bit_range_range.start, + self.skip, + &mut self.non_skipped_bits, + ) { + // Get return type + Ok(index) => Some(Ok(Member::BitFlag(BitFlag { + index, + rustdoc: self.rustdoc.drain(..).collect(), + identifier: ident.clone(), + // Set `self.skip` to `false` returning the previous value. + skip: std::mem::replace(&mut self.skip, false), + }))), + Err(err) => { + self.error = true; + Some(Err(err)) + } + } + } + // Bit range: [literal, punct, punct, literal] + [TokenTree::Literal(start_token), TokenTree::Punct(d1), TokenTree::Punct(d2), TokenTree::Literal(stop_token)] + if d1.as_char() == '.' && d2.as_char() == '.' => + { + // Checks bit range. + match check_bit_range( + &mut self.existing, + ident, + start_token, + stop_token, + parent_bit_range_range.clone(), + parent_bit_range_range.start, + self.skip, + &mut self.non_skipped_bits, + ) { + // Get return type + Ok(range) => Some(Ok(Member::BitRange(BitRange { + range, + rustdoc: self.rustdoc.drain(..).collect(), + identifier: ident.clone(), + // Set `self.skip` to `false` returning the previous value. + skip: std::mem::replace(&mut self.skip, false), + }))), + Err(err) => { + self.error = true; + Some(Err(err)) + } + } + } + _ => { + self.error = true; + return Some(Err(( + group.span(), + BitFieldMembersParserIterError::IndexingMalformed, + ))); + } + }; + } + // Bit range: [ident, punct, literal, punct, punct, literal] e.g. "sse2: 0..2" + [TokenTree::Ident(ident), TokenTree::Punct(colon), TokenTree::Literal(start_token), TokenTree::Punct(d1), TokenTree::Punct(d2), TokenTree::Literal(stop_token), end @ ..] + if colon.as_char() == ':' && d1.as_char() == '.' && d2.as_char() == '.' => + { + // Move past this identified slice. + if let Err(err) = advance_iter(&mut self.iter, end, 5) { + self.error = true; + return Some(Err(err)); + } + + return match add_bit_range( + &mut self.existing, + ident, + start_token, + stop_token, + self.rustdoc.drain(..).collect(), + 0..self.size, + // Set `self.skip` to `false` returning the previous value. + std::mem::replace(&mut self.skip, false), + &mut self.non_skipped_bits, + ) { + Ok(member) => Some(Ok(member)), + Err(err) => { + self.error = true; + Some(Err(err)) + } + }; + } + // Bit flag: [ ident, punct, literal] e.g. "sse2: 0" + [TokenTree::Ident(ident), TokenTree::Punct(colon), TokenTree::Literal(index_token), end @ ..] + if colon.as_char() == ':' => + { + // Move past this identified slice. + if let Err(err) = advance_iter(&mut self.iter, end, 2) { + self.error = true; + return Some(Err(err)); + } + + return match add_bit_flag( + &mut self.existing, + ident, + index_token, + self.rustdoc.drain(..).collect(), + 0..self.size, + // Set `self.skip` to `false` returning the previous value. + std::mem::replace(&mut self.skip, false), + &mut self.non_skipped_bits, + ) { + Ok(member) => Some(Ok(member)), + Err(err) => { + self.error = true; + Some(Err(err)) + } + }; + } + // [ punct, group, .. ] e.g. `#[skip]` or `#[doc=".."]` + [TokenTree::Punct(punct), TokenTree::Group(group), ..] + if punct.as_char() == '#' && group.delimiter() == Delimiter::Bracket => + { + // Move past this identified slice. + self.iter.nth(1); + + let group_vec = group.stream().into_iter().collect::>(); + + match group_vec.as_slice() { + // Skip attribute e.g. `#[skip]` + [TokenTree::Ident(skip_ident)] if skip_ident.to_string() == "skip" => { + self.skip = true; + } + // Rustdoc e.g. `#[doc=".."]` + [TokenTree::Ident(group_ident), TokenTree::Punct(group_punct), TokenTree::Literal(group_lit)] + if group_ident.to_string() == "doc" && group_punct.as_char() == '=' => + { + // Check for then remove " from start and end of string. + let comment_unenclosed = { + let group_string = group_lit.to_string(); + let mut chars = group_string.chars(); + if let (Some('"'), Some('"')) = (chars.next(), chars.next_back()) { + String::from(chars.as_str()) + } else { + self.error = true; + return Some(Err(( + group_lit.span(), + BitFieldMembersParserIterError::RustdocUnenclosed, + ))); + } + }; + // Trim space leading spaces. + // E.g. A coment like `/// abcde` will become `" abcde"` and we want + // `abcde`. + let comment_trimmed = comment_unenclosed.trim_start(); + // We append to the rustdoc string. When we hit a bit flag or field, we + // use the rustdoc string for this flag or + // field, then empty the rustdoc string. + self.rustdoc.push_str(comment_trimmed); + self.rustdoc.push(' '); + } + _ => { + self.error = true; + return Some(Err(( + group.span(), + BitFieldMembersParserIterError::UnknownAttribute, + ))); + } + } + } + // On an exhausted iterator return none. + [] => return None, + // https://doc.rust-lang.org/proc_macro/struct.Span.html#method.join is curretly + // unstable, but when it is stablized we should collect and join spans of remaining + // element for this error message. + _ => { + self.error = true; + return Some(Err(( + Span::call_site(), + BitFieldMembersParserIterError::BadlyDefinedMembers( + self.iter + .clone() + .map(std::string::ToString::to_string) + .collect::>(), + ), + ))); + } + } + } + } +} +/// Create bit range member. +#[allow(clippy::too_many_arguments)] +fn add_bit_range( + existing: &mut HashMap>>, + ident: &Ident, + start_token: &Literal, + stop_token: &Literal, + rustdoc: String, + valid_range: Range, + skip: bool, + non_skipped_bits: &mut [bool], +) -> Result> { + // Checks bit range. + let range = check_bit_range( + existing, + ident, + start_token, + stop_token, + valid_range, + 0, + skip, + non_skipped_bits, + )?; + // Get return type + let member = Member::BitRange(BitRange { + range, + rustdoc, + identifier: ident.clone(), + skip, + }); + Ok(member) +} +/// Create bit flag member. +fn add_bit_flag( + existing: &mut HashMap>>, + ident: &Ident, + index_token: &Literal, + rustdoc: String, + valid_range: Range, + skip: bool, + non_skipped_bits: &mut [bool], +) -> Result> { + // Checks bit flag. + let index = check_bit_flag( + existing, + ident, + index_token, + valid_range, + 0, + skip, + non_skipped_bits, + )?; + // Get return type + let member = Member::BitFlag(BitFlag { + index, + rustdoc, + identifier: ident.clone(), + skip, + }); + Ok(member) +} + +/// Check if identifer not used and index valid. +fn check_bit_flag( + existing: &mut HashMap>>, + ident: &Ident, + index_token: &Literal, + valid_range: Range, + offset: u8, + skip: bool, + non_skipped_bits: &mut [bool], +) -> Result> { + // Check index + let index = index_flag(index_token, valid_range, offset) + .map_err(|(span, err)| (span, BitFieldMembersParserIterError::IndexFlag(err)))?; + + // Check indentifier not already used. + check_ident(existing, ident, None)?; + + // Checks that if this member is non-skipped it does not overlap with another non-skipped + // member. + // Also updates `non_skipped_bits[index]` to be true if this member is non-skipped. + // + // `non_skipped_bits` is initialized to a length of atleast `valid_range.end`. + #[allow(clippy::indexing_slicing)] + let old = std::mem::replace(&mut non_skipped_bits[usize::from(index)], !skip); + // If the bits where already used in a non-skipped member and this member is non-skipped. + if old && !skip { + return Err(( + index_token.span(), + BitFieldMembersParserIterError::OverlappingNonSkipped, + )); + } + + Ok(index) +} + +/// Checks if identifier not used and indices valid. +#[allow(clippy::too_many_arguments)] +fn check_bit_range( + existing: &mut HashMap>>, + ident: &Ident, + start_token: &Literal, + stop_token: &Literal, + valid_range: Range, + offset: u8, + skip: bool, + non_skipped_bits: &mut [bool], +) -> Result, crate::ProcError> { + // Check indices + let bit_range_range = index_field(start_token, stop_token, valid_range, offset) + .map_err(|(span, err)| (span, BitFieldMembersParserIterError::IndexField(err)))?; + + // Check indentifier not already used. + check_ident(existing, ident, Some(bit_range_range.clone()))?; + + // `non_skipped_bits` is initialized to a length of atleast `valid_range.end`. + #[allow(clippy::indexing_slicing)] + for bit in + &mut non_skipped_bits[usize::from(bit_range_range.start)..usize::from(bit_range_range.end)] + { + if *bit && !skip { + // Join the spans of `start_token` and `start_token` when span joining is stabilized. + return Err(( + start_token.span(), + BitFieldMembersParserIterError::OverlappingNonSkipped, + )); + } + *bit = !skip; + } + + Ok(bit_range_range) +} + +/// Check indentifier not already used. +fn check_ident( + existing: &mut HashMap>>, + ident: &Ident, + range: Option>, +) -> Result<(), (Span, BitFieldMembersParserIterError)> { + if existing.insert(ident.to_string(), range).is_some() { + Err(( + ident.span(), + BitFieldMembersParserIterError::DuplicateIdentifier, + )) + } else { + Ok(()) + } +} + +/// Error type for [`index_field`]. +#[derive(Debug, thiserror::Error)] +pub(crate) enum IndexFieldError { + /// Failed to parse token for start index. + #[error("Failed to parse token for start index: {0}")] + ParseStart(std::num::ParseIntError), + /// Start index outside of valid range. + #[error("Start index ({start}) outside of valid range ({valid_range:?}).")] + InvalidStart { + /// Parsed start index. + start: u8, + /// Valid range that `start` lies outside of. + valid_range: Range, + }, + /// Failed to parse token for stop index. + #[error("Failed to parse token for stop index: {0}")] + ParseStop(std::num::ParseIntError), + /// Stop index outside of valid range. + #[error("Stop index ({stop}) outside of valid range ({valid_range:?}).")] + InvalidStop { + /// Parsed stop index. + stop: u8, + /// Valid range that `stop` lies outside of. + valid_range: std::ops::RangeInclusive, + }, +} + +/// For bit field indices, checks if they are both within the range of the data type and the stop is +/// greater than or equal to the start then returns them as `u8`s. +#[allow(clippy::integer_arithmetic, clippy::arithmetic_side_effects)] +fn index_field( + start_token: &Literal, + stop_token: &Literal, + valid_range: Range, + offset: u8, +) -> Result, crate::ProcError> { + // Get start, checking if in range of data type. + let start = match start_token.to_string().parse::() { + Ok(s) if valid_range.contains(&(s + offset)) => Ok(s + offset), + Ok(s) => Err(( + start_token.span(), + IndexFieldError::InvalidStart { + start: s, + valid_range: valid_range.clone(), + }, + )), + Err(err) => Err((start_token.span(), IndexFieldError::ParseStart(err))), + }?; + // Get stop, checking if in range of data type. + let stop = match stop_token.to_string().parse::() { + Ok(s) if (start..=valid_range.end).contains(&(s + offset)) => Ok(s + offset), + Ok(s) => Err(( + stop_token.span(), + IndexFieldError::InvalidStop { + stop: s, + valid_range: start..=valid_range.end, + }, + )), + Err(err) => Err((stop_token.span(), IndexFieldError::ParseStop(err))), + }?; + + Ok(start..stop) +} + +/// Error type for [`index_field`]. +#[derive(Debug, thiserror::Error)] +pub(crate) enum IndexFlagError { + /// Failed to parse token for index. + #[error("Failed to parse token for index: {0}")] + Parse(std::num::ParseIntError), + /// Index outside of valid range. + #[error("Index ({index}) outside valid range ({valid_range:?}).")] + Invalid { + /// Parsed index. + index: u8, + /// Valid range that `index` lies outside of. + valid_range: Range, + }, +} + +/// For abit flag index, checks if it is within the range of the data type, then returns it as a +/// `u8`. +#[allow(clippy::integer_arithmetic, clippy::arithmetic_side_effects)] +fn index_flag( + index: &Literal, + valid_range: Range, + offset: u8, +) -> Result> { + // Get index, checking if in range of data type. + match index.to_string().parse::() { + Ok(s) if valid_range.contains(&(s + offset)) => Ok(s + offset), + Ok(s) => Err(( + index.span(), + IndexFlagError::Invalid { + index: s, + valid_range, + }, + )), + Err(err) => Err((index.span(), IndexFlagError::Parse(err))), + } +} + +#[cfg(test)] +mod tests { + #![allow( + non_snake_case, + clippy::dbg_macro, + clippy::unwrap_used, + clippy::as_conversions, + clippy::shadow_unrelated + )] + + use proc_macro2::{Group, Ident, Literal, Punct, Spacing, TokenTree}; + + use super::*; + + // Construct an ident with a given string. + fn ident(s: &str) -> Ident { + Ident::new(s, Span::call_site()) + } + fn punct(c: char) -> Punct { + Punct::new(c, Spacing::Alone) + } + fn rustdoc(s: &str) -> [TokenTree; 2] { + [ + TokenTree::Punct(punct('#')), + TokenTree::Group(Group::new(Delimiter::Bracket, quote::quote! { doc=#s })), + ] + } + fn field(name: &str, start: u8, stop: u8) -> [TokenTree; 6] { + [ + TokenTree::Ident(ident(name)), + TokenTree::Punct(punct(':')), + TokenTree::Literal(Literal::u8_unsuffixed(start)), + TokenTree::Punct(punct('.')), + TokenTree::Punct(punct('.')), + TokenTree::Literal(Literal::u8_unsuffixed(stop)), + ] + } + fn field_comma(name: &str, start: u8, stop: u8) -> [TokenTree; 7] { + [ + TokenTree::Ident(ident(name)), + TokenTree::Punct(punct(':')), + TokenTree::Literal(Literal::u8_unsuffixed(start)), + TokenTree::Punct(punct('.')), + TokenTree::Punct(punct('.')), + TokenTree::Literal(Literal::u8_unsuffixed(stop)), + TokenTree::Punct(punct(',')), + ] + } + fn flag(name: &str, index: u8) -> [TokenTree; 3] { + [ + TokenTree::Ident(ident(name)), + TokenTree::Punct(punct(':')), + TokenTree::Literal(Literal::u8_unsuffixed(index)), + ] + } + fn flag_comma(name: &str, index: u8) -> [TokenTree; 4] { + [ + TokenTree::Ident(ident(name)), + TokenTree::Punct(punct(':')), + TokenTree::Literal(Literal::u8_unsuffixed(index)), + TokenTree::Punct(punct(',')), + ] + } + + #[test] + fn bit_field_members_parser_empty() { + let tokens = &[]; + let mut parser = BitFieldMembersParser::from((8, tokens.iter())); + assert!(matches!(parser.next(), None)); + } + #[test] + fn bit_field_members_parser_field() { + let tokens = &field("field", 0, 1); + let mut parser = BitFieldMembersParser::from((8, tokens.iter())); + assert!( + matches!(parser.next(),Some(Ok(Member::BitRange(BitRange { range: Range { start: 0, end: 1 }, rustdoc, identifier: ident, skip: false }))) if rustdoc.is_empty() && ident == "field") + ); + assert!(matches!(parser.next(), None)); + } + #[test] + fn bit_field_members_parser_field_comma() { + let tokens = &field_comma("field", 0, 1); + let mut parser = BitFieldMembersParser::from((8, tokens.iter())); + assert!( + matches!(parser.next(),Some(Ok(Member::BitRange(BitRange { range: Range { start: 0, end: 1 }, rustdoc, identifier: ident, skip: false }))) if rustdoc.is_empty() && ident == "field") + ); + assert!(matches!(parser.next(), None)); + } + #[test] + fn bit_field_members_parser_field_no_comma() { + let tokens = &[field("field1", 0, 1), field("field2", 1, 2)].concat(); + let mut parser = BitFieldMembersParser::from((8, tokens.iter())); + assert!(matches!( + parser.next(), + Some(Err((_, BitFieldMembersParserIterError::ExpectedComma))) + )); + assert!(matches!(parser.next(), None)); + } + #[test] + fn bit_field_members_parser_flag_duplicate() { + let tokens = &[ + field_comma("field", 0, 1).as_slice(), + field("field", 1, 2).as_slice(), + ] + .concat(); + let mut parser = BitFieldMembersParser::from((8, tokens.iter())); + assert!( + matches!(parser.next(),Some(Ok(Member::BitRange(BitRange { range: Range { start: 0, end: 1 }, rustdoc, identifier: ident, skip: false }))) if rustdoc.is_empty() && ident == "field") + ); + assert!(matches!( + parser.next(), + Some(Err(( + _, + BitFieldMembersParserIterError::DuplicateIdentifier + ))) + )); + assert!(matches!(parser.next(), None)); + } + #[test] + fn bit_field_members_parser_flag_comma_duplicate() { + let tokens = &[field_comma("field", 0, 1), field_comma("field", 1, 2)].concat(); + let mut parser = BitFieldMembersParser::from((8, tokens.iter())); + assert!( + matches!(parser.next(),Some(Ok(Member::BitRange(BitRange { range: Range { start: 0, end: 1 }, rustdoc, identifier: ident, skip: false }))) if rustdoc.is_empty() && ident == "field") + ); + assert!(matches!( + parser.next(), + Some(Err(( + _, + BitFieldMembersParserIterError::DuplicateIdentifier + ))) + )); + dbg!(parser.next()); + assert!(matches!(parser.next(), None)); + } + #[test] + fn bit_field_members_parser_field_seperating_comma() { + let tokens = &[ + field_comma("field1", 0, 1).as_slice(), + field("field2", 1, 2).as_slice(), + ] + .concat(); + let mut parser = BitFieldMembersParser::from((8, tokens.iter())); + assert!( + matches!(parser.next(),Some(Ok(Member::BitRange(BitRange { range: Range { start: 0, end: 1 }, rustdoc, identifier: ident, skip: false }))) if rustdoc.is_empty() && ident == "field1") + ); + assert!( + matches!(parser.next(),Some(Ok(Member::BitRange(BitRange { range: Range { start: 1, end: 2 }, rustdoc, identifier: ident, skip: false }))) if rustdoc.is_empty() && ident == "field2") + ); + assert!(matches!(parser.next(), None)); + } + #[test] + fn bit_field_members_parser_field_rustdoc() { + let tokens = &[ + rustdoc("some docs").as_slice(), + field("field", 0, 1).as_slice(), + ] + .concat(); + let mut parser = BitFieldMembersParser::from((8, tokens.iter())); + assert!( + matches!(parser.next(),Some(Ok(Member::BitRange(BitRange { range: Range { start: 0, end: 1 }, rustdoc, identifier: ident, skip: false }))) if rustdoc == "some docs " && ident == "field") + ); + assert!(matches!(parser.next(), None)); + } + + #[test] + fn bit_field_members_parser_flag() { + let tokens = &flag("flag", 0); + let mut parser = BitFieldMembersParser::from((8, tokens.iter())); + assert!( + matches!(parser.next(),Some(Ok(Member::BitFlag(BitFlag { index: 0, rustdoc, identifier: ident, skip: false }))) if rustdoc.is_empty() && ident == "flag") + ); + assert!(matches!(parser.next(), None)); + } + #[test] + fn bit_field_members_parser_flag_comma() { + let tokens = &flag_comma("flag", 0); + let mut parser = BitFieldMembersParser::from((8, tokens.iter())); + assert!( + matches!(parser.next(),Some(Ok(Member::BitFlag(BitFlag { index: 0, rustdoc, identifier: ident, skip: false }))) if rustdoc.is_empty() && ident == "flag") + ); + assert!(matches!(parser.next(), None)); + } + #[test] + fn bit_field_members_parser_flag_no_comma() { + let tokens = &[flag("flag1", 0), flag("flag2", 1)].concat(); + let mut parser = BitFieldMembersParser::from((8, tokens.iter())); + assert!(matches!( + parser.next(), + Some(Err((_, BitFieldMembersParserIterError::ExpectedComma))) + )); + assert!(matches!(parser.next(), None)); + } + #[test] + fn bit_field_members_parser_flag_seperating_comma() { + let tokens = &[ + flag_comma("flag1", 0).as_slice(), + flag("flag2", 1).as_slice(), + ] + .concat(); + let mut parser = BitFieldMembersParser::from((8, tokens.iter())); + assert!( + matches!(parser.next(),Some(Ok(Member::BitFlag(BitFlag { index: 0, rustdoc, identifier: ident, skip: false }))) if rustdoc.is_empty() && ident == "flag1") + ); + assert!( + matches!(parser.next(),Some(Ok(Member::BitFlag(BitFlag { index: 1, rustdoc, identifier: ident, skip: false }))) if rustdoc.is_empty() && ident == "flag2") + ); + assert!(matches!(parser.next(), None)); + } + #[test] + fn bit_field_members_parser_flag_rustdoc() { + let tokens = &[rustdoc("some docs").as_slice(), flag("flag", 0).as_slice()].concat(); + let mut parser = BitFieldMembersParser::from((8, tokens.iter())); + assert!( + matches!(parser.next(),Some(Ok(Member::BitFlag(BitFlag { index: 0, rustdoc, identifier: ident, skip: false }))) if rustdoc == "some docs " && ident == "flag") + ); + assert!(matches!(parser.next(), None)); + } + + #[test] + fn bit_field_members_parser_mixed() { + let tokens = &[ + rustdoc("some docs").as_slice(), + flag_comma("flag1", 0).as_slice(), + rustdoc("some more docs").as_slice(), + field_comma("field1", 1, 2).as_slice(), + flag_comma("flag2", 3).as_slice(), + rustdoc("some extra docs").as_slice(), + field_comma("field2", 4, 5).as_slice(), + ] + .concat(); + let mut parser = BitFieldMembersParser::from((8, tokens.iter())); + assert!( + matches!(parser.next(),Some(Ok(Member::BitFlag(BitFlag { index: 0, rustdoc, identifier: ident, skip: false }))) if rustdoc == "some docs " && ident == "flag1") + ); + assert!( + matches!(parser.next(),Some(Ok(Member::BitRange(BitRange { range: Range { start: 1, end: 2, }, rustdoc, identifier: ident, skip: false }))) if rustdoc == "some more docs " && ident == "field1") + ); + assert!( + matches!(parser.next(),Some(Ok(Member::BitFlag(BitFlag { index: 3, rustdoc, identifier: ident, skip: false }))) if rustdoc.is_empty() && ident == "flag2") + ); + assert!( + matches!(parser.next(),Some(Ok(Member::BitRange(BitRange { range: Range { start: 4, end: 5, }, rustdoc, identifier: ident, skip: false }))) if rustdoc == "some extra docs " && ident == "field2") + ); + assert!(matches!(parser.next(), None)); + } + + #[test] + fn index_field_suffixed() { + let res = index_field(&Literal::u8_suffixed(0), &Literal::u8_suffixed(4), 0..8, 0); + dbg!(&res); + assert!(matches!(res, Err((_, IndexFieldError::ParseStart(_))))); + + let res = index_field( + &Literal::u8_unsuffixed(0), + &Literal::u8_suffixed(4), + 0..8, + 0, + ); + dbg!(&res); + assert!(matches!(res, Err((_, IndexFieldError::ParseStop(_))))); + } + #[test] + fn index_field_unsuffixed() { + let res = index_field( + &Literal::u8_unsuffixed(0), + &Literal::u8_unsuffixed(4), + 0..8, + 0, + ); + dbg!(&res); + assert!(matches!(res, Ok(Range { start: 0, end: 4 }))); + } + + #[test] + fn index_field_start_outside() { + let res = index_field( + &Literal::u8_unsuffixed(8), + &Literal::u8_unsuffixed(4), + 0..8, + 0, + ); + dbg!(&res); + assert!( + matches!(res,Err((_,IndexFieldError::InvalidStart { start: 8, valid_range })) if (valid_range == (0..8))) + ); + } + #[test] + fn index_field_stop_outside() { + let res = index_field( + &Literal::u8_unsuffixed(0), + &Literal::u8_unsuffixed(9), + 0..8, + 0, + ); + dbg!(&res); + assert!( + matches!(res,Err((_,IndexFieldError::InvalidStop { stop: 9, valid_range })) if (valid_range == (0..=8)) ) + ); + } + #[test] + fn index_field_stop_before() { + let res = index_field( + &Literal::u8_unsuffixed(4), + &Literal::u8_unsuffixed(3), + 0..8, + 0, + ); + dbg!(&res); + assert!( + matches!(res,Err((_,IndexFieldError::InvalidStop{ stop: 3, valid_range } )) if (valid_range == (4..=8))) + ); + } + #[test] + fn index_flag_suffixed() { + let res = index_flag(&Literal::u8_suffixed(4), 0..8, 0); + dbg!(&res); + assert!(matches!(res, Err((_, IndexFlagError::Parse(_))))); + } + #[test] + fn index_flag_unsuffixed() { + let res = index_flag(&Literal::u8_unsuffixed(4), 0..8, 0); + dbg!(&res); + assert!(matches!(res, Ok(4))); + } + #[test] + fn index_flag_outside() { + let res = index_flag(&Literal::u8_unsuffixed(8), 0..8, 0); + dbg!(&res); + assert!( + matches!(res,Err((_,IndexFlagError::Invalid { index: 8, valid_range })) if (valid_range == (0..8))) + ); + } +} diff --git a/src/bit-fields-macros/src/utils.rs b/src/bit-fields-macros/src/utils.rs new file mode 100644 index 00000000000..4754770779c --- /dev/null +++ b/src/bit-fields-macros/src/utils.rs @@ -0,0 +1,345 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use std::convert::TryFrom; +use std::default::Default; +use std::fmt; +use std::str::FromStr; + +use proc_macro2::{Ident, Span, TokenStream}; +use quote::quote; + +/// Type used to ensure safety by avoiding some arithmetic operations. +#[derive(Debug, Clone, Copy)] +pub struct DataTypeToken { + /// [`DataType`] + ty: DataType, + /// [`proc_macro2::Span`] + span: Span, +} + +impl DataTypeToken { + // When const trait implementations are stabilized this should be + // `impl const From for u8`. + /// Returns the size in bits of the data type. + pub const fn size(self) -> u8 { + self.ty.size() + } + /// Returns base data type. + pub fn base(self) -> TokenStream { + self.ty.base() + } +} + +/// Error type for [`>::try_from`]. +type DataTypeTokenTryFromError = crate::ProcError; + +impl TryFrom for DataTypeToken { + type Error = DataTypeTokenTryFromError; + fn try_from(ident: Ident) -> Result { + match DataType::from_str(&ident.to_string()) { + Ok(ty) => Ok(Self { + ty, + span: ident.span(), + }), + Err(err) => Err((ident.span(), err)), + } + } +} + +impl quote::ToTokens for DataTypeToken { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.extend(std::iter::once(proc_macro2::TokenTree::Ident(Ident::new( + &self.ty.to_string(), + self.span, + )))); + } +} + +impl Default for DataTypeToken { + fn default() -> Self { + Self { + ty: DataType::default(), + span: Span::call_site(), + } + } +} + +/// Type used to ensure safety by avoiding some arithmetic operations. +/// +/// Without this type we would parse and store the size of the bit field as a `u8`. The following +/// operations would then use the generic `u8` implementations which the compiler cannot guarantee +/// are safe. By using this enum to restrict the type we can avoid these operations. This is a +/// small point, but helps a little. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DataType { + /// `u8` + U8, + /// `u16` + U16, + /// `u32` + U32, + /// `u64` + U64, + /// `u128` + U128, +} + +impl DataType { + // When const trait implementations are stabilized this should be + // `impl const From for u8`. + /// Returns the size in bits of the data type. + pub const fn size(self) -> u8 { + match self { + Self::U8 => 8, + Self::U16 => 16, + Self::U32 => 32, + Self::U64 => 64, + Self::U128 => 128, + } + } + /// Returns base data type. + pub fn base(self) -> TokenStream { + match self { + Self::U8 => quote! { u8 }, + Self::U16 => quote! { u16 }, + Self::U32 => quote! { u32 }, + Self::U64 => quote! { u64 }, + Self::U128 => quote! { u128 }, + } + } +} + +impl Default for DataType { + fn default() -> Self { + Self::U8 + } +} + +impl fmt::Display for DataType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Self::U8 => write!(f, "u8"), + Self::U16 => write!(f, "u16"), + Self::U32 => write!(f, "u32"), + Self::U64 => write!(f, "u64"), + Self::U128 => write!(f, "u128"), + } + } +} + +impl FromStr for DataType { + type Err = DataTypeTryFromError; + fn from_str(s: &str) -> Result { + match s { + "u8" => Ok(Self::U8), + "u16" => Ok(Self::U16), + "u32" => Ok(Self::U32), + "u64" => Ok(Self::U64), + "u128" => Ok(Self::U128), + _ => Err(DataTypeTryFromError), + } + } +} + +/// Error type for `impl TryFrom<&str> for DataType` +#[derive(Debug, PartialEq, Eq, thiserror::Error)] +#[error( + "Bad backing data type given. Only accepted types are `u8`, `u16`, `u32`, `u64` and `u128`." +)] +pub struct DataTypeTryFromError; + +/// Utility used to assembly the display string +#[derive(Debug, Default)] +pub struct MultiLineString(pub Vec); + +impl MultiLineString { + /// Pushes `MultiLineString` on to `self`, if the given `other` is deeper than `self` it will + /// expand down to accommodate. + pub fn push(&mut self, other: &Self) { + // Split `other` into slice to join to `self` and slice to extend `self`. + let (join, extend) = other.0.split_at(self.0.len()); + + // Lines where both `self` and `other` have strings, join them + let joined = self.0.iter().zip(join).map(|(x, y)| format!("{x}{y}")); + + // Lines `other` extends below `self` add them + let chained = joined.chain(extend.iter().cloned()); + + // Collect into new string + self.0 = chained.collect(); + } + /// Pushes a `&str` onto `self`. + pub fn push_str(&mut self, s: &str) { + self.push(&MultiLineString::from(s)); + } +} + +// Un-fallable `FromStr`. +impl From<&str> for MultiLineString { + fn from(s: &str) -> Self { + Self(s.split('\n').map(String::from).collect()) + } +} + +impl fmt::Display for MultiLineString { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + for line in &self.0 { + writeln!(f, "{}", line)?; + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + #![allow( + non_snake_case, + clippy::dbg_macro, + clippy::unwrap_used, + clippy::as_conversions, + clippy::shadow_unrelated + )] + + use proc_macro2::{Ident, Span}; + + use super::*; + + #[test] + fn data_type_token_debug() { + assert_eq!( + format!( + "{:?}", + DataTypeToken { + ty: DataType::U8, + span: Span::call_site() + } + ), + "DataTypeToken { ty: U8, span: Span }" + ); + assert_eq!( + format!( + "{:?}", + DataTypeToken { + ty: DataType::U16, + span: Span::call_site() + } + ), + "DataTypeToken { ty: U16, span: Span }" + ); + assert_eq!( + format!( + "{:?}", + DataTypeToken { + ty: DataType::U32, + span: Span::call_site() + } + ), + "DataTypeToken { ty: U32, span: Span }" + ); + assert_eq!( + format!( + "{:?}", + DataTypeToken { + ty: DataType::U64, + span: Span::call_site() + } + ), + "DataTypeToken { ty: U64, span: Span }" + ); + assert_eq!( + format!( + "{:?}", + DataTypeToken { + ty: DataType::U128, + span: Span::call_site() + } + ), + "DataTypeToken { ty: U128, span: Span }" + ); + } + #[test] + fn data_type_token_try_from_ident() { + let a = DataTypeToken::try_from(Ident::new("u8", Span::call_site())); + assert!(matches!(a,Ok(x) if x.ty == DataType::U8)); + + let a = DataTypeToken::try_from(Ident::new("u16", Span::call_site())); + assert!(matches!(a,Ok(x) if x.ty == DataType::U16)); + + let a = DataTypeToken::try_from(Ident::new("u32", Span::call_site())); + assert!(matches!(a,Ok(x) if x.ty == DataType::U32)); + + let a = DataTypeToken::try_from(Ident::new("u64", Span::call_site())); + assert!(matches!(a,Ok(x) if x.ty == DataType::U64)); + + let a = DataTypeToken::try_from(Ident::new("u128", Span::call_site())); + assert!(matches!(a,Ok(x) if x.ty == DataType::U128)); + + let a = DataTypeToken::try_from(Ident::new("u256", Span::call_site())); + assert!(matches!(a, Err((_, DataTypeTryFromError)))); + } + + #[test] + fn data_type_debug() { + assert_eq!(format!("{:?}", DataType::U8), "U8"); + assert_eq!(format!("{:?}", DataType::U16), "U16"); + assert_eq!(format!("{:?}", DataType::U32), "U32"); + assert_eq!(format!("{:?}", DataType::U64), "U64"); + assert_eq!(format!("{:?}", DataType::U128), "U128"); + } + #[test] + fn data_type_display() { + assert_eq!(DataType::U8.to_string(), "u8"); + assert_eq!(DataType::U16.to_string(), "u16"); + assert_eq!(DataType::U32.to_string(), "u32"); + assert_eq!(DataType::U64.to_string(), "u64"); + assert_eq!(DataType::U128.to_string(), "u128"); + } + #[test] + fn data_type_from_str() { + assert_eq!(DataType::from_str("u8"), Ok(DataType::U8)); + assert_eq!(DataType::from_str("u16"), Ok(DataType::U16)); + assert_eq!(DataType::from_str("u32"), Ok(DataType::U32)); + assert_eq!(DataType::from_str("u64"), Ok(DataType::U64)); + assert_eq!(DataType::from_str("u128"), Ok(DataType::U128)); + assert_eq!(DataType::from_str("u256"), Err(DataTypeTryFromError)); + } + + #[test] + fn data_type_try_from_error_debug() { + assert_eq!( + format!("{:?}", DataTypeTryFromError), + "DataTypeTryFromError" + ); + } + #[test] + fn data_type_try_from_error_display() { + assert_eq!( + DataTypeTryFromError.to_string(), + "Bad backing data type given. Only accepted types are `u8`, `u16`, `u32`, `u64` and \ + `u128`." + ); + } + + #[test] + fn multi_line_string_debug() { + assert_eq!( + format!("{:?}", MultiLineString::default()), + "MultiLineString([])" + ); + } + #[test] + fn multi_line_string_match() { + let mut mls = MultiLineString(Vec::new()); + mls.push_str("1 4\n2 5\n3 6"); + mls.push_str(" 7 10\n 8 11\n 9 12"); + assert_eq!(mls.to_string(), "1 4 7 10\n2 5 8 11\n3 6 9 12\n"); + } + #[test] + fn multi_line_string_mismatch() { + let mut mls = MultiLineString(Vec::new()); + mls.push_str("1 2 3\n4 5 6"); + mls.push_str(" 7 10\n 8 11\n9 12"); + assert_eq!(mls.to_string(), "1 2 3 7 10\n4 5 6 8 11\n9 12\n"); + } +} diff --git a/src/bit-fields/Cargo.toml b/src/bit-fields/Cargo.toml new file mode 100644 index 00000000000..28cb0de0b5d --- /dev/null +++ b/src/bit-fields/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "bit-fields" +version = "0.1.0" +authors = ["Amazon Firecracker team "] +edition = "2021" +license = "Apache-2.0" +keywords = ["bit", "bitmask", "bitflags", "flags"] +repository = "https://github.com/firecracker-microvm/firecracker" +homepage = "https://firecracker-microvm.github.io/" +documentation = "https://docs.rs/bit-fields" +description = "A macro to generate structures which support bit flags and sub-bytes ranges." +exclude = ["/src/example.rs"] + +[dependencies] +bit-fields-macros = { path = "../bit-fields-macros" } +serde = { version = "1.0.139", features=["derive"] } +thiserror = "1.0.32" + +[dev-dependencies] +serde_json = "1.0.83" +rand = "0.8.5" \ No newline at end of file diff --git a/src/bit-fields/src/bit.rs b/src/bit-fields/src/bit.rs new file mode 100644 index 00000000000..1580ac18860 --- /dev/null +++ b/src/bit-fields/src/bit.rs @@ -0,0 +1,456 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/// A type interface for a single bit. +#[derive(Debug, Clone)] +pub struct Bit<'a, T, const P: u8>(pub &'a T); + +/// A type interface for a single bit. +#[allow(clippy::module_name_repetitions)] +#[derive(Debug)] +pub struct BitMut<'a, T, const P: u8>(pub &'a mut T); + +/// Macro for defining `impl Display for Bit`. +macro_rules! bit_display { + ($x:ty) => { + impl std::fmt::Display for Bit<'_, $x, P> { + #[doc = concat!(" + ``` + use bit_fields::Bit; + let x = 5", stringify!($x), "; + assert_eq!(Bit::<_,0>(&x).to_string(),true.to_string()); + assert_eq!(Bit::<_,1>(&x).to_string(),false.to_string()); + assert_eq!(Bit::<_,2>(&x).to_string(),true.to_string()); + ``` + ")] + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", bool::from(self)) + } + } + }; +} + +bit_display!(u128); +bit_display!(u64); +bit_display!(u32); +bit_display!(u16); +bit_display!(u8); + +/// Macro for defining `impl Display for BitMut`. +macro_rules! bit_mut_display { + ($x:ty) => { + impl std::fmt::Display for BitMut<'_, $x, P> { + #[doc = concat!(" + ``` + use bit_fields::BitMut; + let mut x = 5", stringify!($x), "; + assert_eq!(BitMut::<_,0>(&mut x).to_string(),true.to_string()); + assert_eq!(BitMut::<_,1>(&mut x).to_string(),false.to_string()); + assert_eq!(BitMut::<_,2>(&mut x).to_string(),true.to_string()); + ``` + ")] + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", bool::from(self)) + } + } + }; +} + +bit_mut_display!(u128); +bit_mut_display!(u64); +bit_mut_display!(u32); +bit_mut_display!(u16); +bit_mut_display!(u8); + +/// Macro for defining `impl Bit`. +macro_rules! bit { + ($x:ty) => { + impl Bit<'_, $x, P> { + pub const MASK: $x = 1 << P; + + #[doc = concat!(" + Returns the value of the bit. + ``` + use bit_fields::Bit; + let x = 5", stringify!($x), "; + assert_eq!(Bit::<_,0>(&x).read(),true); + assert_eq!(Bit::<_,1>(&x).read(),false); + assert_eq!(Bit::<_,2>(&x).read(),true); + ``` + ")] + #[must_use] + #[inline] + pub fn read(&self) -> bool { + bool::from(self) + } + + #[doc = concat!(" + Returns if the bit is 1. + ``` + use bit_fields::Bit; + let x = 5", stringify!($x), "; + assert_eq!(Bit::<_,0>(&x).is_on(),true); + assert_eq!(Bit::<_,1>(&x).is_on(),false); + assert_eq!(Bit::<_,2>(&x).is_on(),true); + ``` + ")] + #[must_use] + #[inline] + pub fn is_on(&self) -> bool { + bool::from(self) + } + + #[doc = concat!(" + Returns if the bit is 0. + ``` + use bit_fields::Bit; + let x = 5", stringify!($x), "; + assert_eq!(Bit::<_,0>(&x).is_off(),false); + assert_eq!(Bit::<_,1>(&x).is_off(),true); + assert_eq!(Bit::<_,2>(&x).is_off(),false); + ``` + ")] + #[must_use] + #[inline] + pub fn is_off(&self) -> bool { + !bool::from(self) + } + } + }; +} + +bit!(u128); +bit!(u64); +bit!(u32); +bit!(u16); +bit!(u8); + +/// Macro for defining `impl BitMut`. +macro_rules! bit_mut { + ($x:ty) => { + impl BitMut<'_, $x, P> { + pub const MASK: $x = 1 << P; + + #[doc = concat!(" + Returns the value of the bit. + ``` + use bit_fields::BitMut; + let mut x = 5", stringify!($x), "; + assert_eq!(BitMut::<_,0>(&mut x).read(),true); + assert_eq!(BitMut::<_,1>(&mut x).read(),false); + assert_eq!(BitMut::<_,2>(&mut x).read(),true); + ``` + ")] + #[must_use] + #[inline] + pub fn read(&self) -> bool { + bool::from(self) + } + + #[doc = concat!(" + Alias for [`Self::set`]. + ``` + use bit_fields::BitMut; + let mut x = 5", stringify!($x), "; + BitMut::<_,0>(&mut x).write(false); + assert_eq!(x,4); + BitMut::<_,1>(&mut x).write(true); + assert_eq!(x,6); + BitMut::<_,2>(&mut x).write(false); + assert_eq!(x,2); + ``` + ")] + #[inline] + pub fn write(&mut self, x: bool) { + self.set(x); + } + + // These values are only evaluated at compile-time, thus a failure can only occur at + // compile-time and would be immediately obvious. Thus it is safe to use arithmetic + // here. + #[doc = concat!(" + Set the bit to 1. + ``` + use bit_fields::BitMut; + let mut x = 5", stringify!($x), "; + BitMut::<_,0>(&mut x).on(); + assert_eq!(x,5); + BitMut::<_,1>(&mut x).on(); + assert_eq!(x,7); + BitMut::<_,2>(&mut x).on(); + assert_eq!(x,7); + ``` + ")] + #[allow(clippy::integer_arithmetic)] + #[inline] + pub fn on(&mut self) { + *self.0 |= Self::MASK; + } + + #[doc = concat!(" + Returns if the bit is 1. + ``` + use bit_fields::BitMut; + let mut x = 5", stringify!($x), "; + assert_eq!(BitMut::<_,0>(&mut x).is_on(),true); + assert_eq!(BitMut::<_,1>(&mut x).is_on(),false); + assert_eq!(BitMut::<_,2>(&mut x).is_on(),true); + ``` + ")] + #[must_use] + #[inline] + pub fn is_on(&self) -> bool { + bool::from(self) + } + + // These values are only evaluated at compile-time, thus a failure can only occur at + // compile-time and would be immediately obvious. Thus it is safe to use arithmetic + // here. + #[doc = concat!(" + Set the bit to 0. + ``` + use bit_fields::BitMut; + let mut x = 5", stringify!($x), "; + BitMut::<_,0>(&mut x).off(); + assert_eq!(x,4); + BitMut::<_,1>(&mut x).off(); + assert_eq!(x,4); + BitMut::<_,2>(&mut x).off(); + assert_eq!(x,0); + ``` + ")] + #[allow(clippy::integer_arithmetic)] + #[inline] + pub fn off(&mut self) { + *self.0 &= !Self::MASK; + } + + #[doc = concat!(" + Returns if the bit is 0. + ``` + use bit_fields::BitMut; + let mut x = 5", stringify!($x), "; + assert_eq!(BitMut::<_,0>(&mut x).is_off(),false); + assert_eq!(BitMut::<_,1>(&mut x).is_off(),true); + assert_eq!(BitMut::<_,2>(&mut x).is_off(),false); + ``` + ")] + #[must_use] + #[inline] + pub fn is_off(&self) -> bool { + !bool::from(self) + } + + // These values are only evaluated at compile-time, thus a failure can only occur at + // compile-time and would be immediately obvious. Thus it is safe to use arithmetic + // here. + #[doc = concat!(" + Flips the value of the bit. + ``` + use bit_fields::BitMut; + let mut x = 5", stringify!($x), "; + BitMut::<_,0>(&mut x).flip(); + assert_eq!(x,4); + BitMut::<_,1>(&mut x).flip(); + assert_eq!(x,6); + BitMut::<_,2>(&mut x).flip(); + assert_eq!(x,2); + ``` + ")] + #[allow(clippy::integer_arithmetic)] + #[inline] + pub fn flip(&mut self) { + *self.0 ^= Self::MASK; + } + + #[doc = concat!(" + Sets the bit. + ``` + use bit_fields::BitMut; + let mut x = 5", stringify!($x), "; + BitMut::<_,0>(&mut x).set(false); + assert_eq!(x,4); + BitMut::<_,1>(&mut x).set(true); + assert_eq!(x,6); + BitMut::<_,2>(&mut x).set(false); + assert_eq!(x,2); + ``` + ")] + #[inline] + pub fn set(&mut self, x: bool) { + if x { + self.on() + } else { + self.off() + } + } + } + }; +} + +bit_mut!(u128); +bit_mut!(u64); +bit_mut!(u32); +bit_mut!(u16); +bit_mut!(u8); + +/// Macro for defining `From` implementations on `Bit`. +macro_rules! bit_from { + ($x:ty) => { + // These values are only evaluated at compile-time, thus a failure can only occur at + // compile-time and would be immediately obvious. Thus it is safe to use arithmetic here. + #[allow(clippy::integer_arithmetic, clippy::as_conversions)] + impl From<&Bit<'_, $x, P>> for bool { + #[doc = concat!(" + ``` + use bit_fields::Bit; + let x = 5", stringify!($x), "; + let bit = Bit::<_,0>(&x); + assert_eq!(bool::from(&bit),true); + let bit = Bit::<_,1>(&x); + assert_eq!(bool::from(&bit),false); + let bit = Bit::<_,2>(&x); + assert_eq!(bool::from(&bit),true); + ``` + ")] + #[inline] + fn from(this: &Bit<'_, $x, P>) -> Self { + (*this.0 & Bit::<$x, P>::MASK) != 0 + } + } + }; +} +bit_from!(u128); +bit_from!(u64); +bit_from!(u32); +bit_from!(u16); +bit_from!(u8); + +/// Macro for defining `From` implementations on `BitMut`. +macro_rules! bit_mut_from { + ($x:ty) => { + // These values are only evaluated at compile-time, thus a failure can only occur at + // compile-time and would be immediately obvious. Thus it is safe to use arithmetic here. + #[allow(clippy::integer_arithmetic, clippy::as_conversions)] + impl From<&BitMut<'_, $x, P>> for bool { + #[doc = concat!(" + ``` + use bit_fields::BitMut; + let mut x = 5", stringify!($x), "; + let bit = BitMut::<_,0>(&mut x); + assert_eq!(bool::from(&bit),true); + let bit = BitMut::<_,1>(&mut x); + assert_eq!(bool::from(&bit),false); + let bit = BitMut::<_,2>(&mut x); + assert_eq!(bool::from(&bit),true); + ``` + ")] + #[inline] + fn from(this: &BitMut<'_, $x, P>) -> Self { + (*this.0 & BitMut::<$x, P>::MASK) != 0 + } + } + }; +} + +bit_mut_from!(u128); +bit_mut_from!(u64); +bit_mut_from!(u32); +bit_mut_from!(u16); +bit_mut_from!(u8); + +/// Macro for defining `PartiaEq` and `Eq` implementations on `Bit`. +macro_rules! bit_eq { + ($x:ty) => { + impl PartialEq for Bit<'_, $x, P> { + #[doc = concat!(" + ``` + use bit_fields::Bit; + let x = 5", stringify!($x), "; + let y = 5", stringify!($x), "; + let a = Bit::<_,0>(&x); + let b = Bit::<_,0>(&y); + + assert_eq!(a,b); + ``` + ")] + #[inline] + fn eq(&self, other: &Self) -> bool { + let a = bool::from(self); + let b = bool::from(other); + a == b + } + } + impl PartialEq for Bit<'_, $x, P> { + #[doc = concat!(" + ``` + use bit_fields::Bit; + let x = 5", stringify!($x), "; + assert_eq!(Bit::<_,0>(&x),true); + assert_eq!(Bit::<_,1>(&x),false); + assert_eq!(Bit::<_,2>(&x),true); + ``` + ")] + #[inline] + fn eq(&self, other: &bool) -> bool { + bool::from(self) == *other + } + } + impl Eq for Bit<'_, $x, P> {} + }; +} + +bit_eq!(u128); +bit_eq!(u64); +bit_eq!(u32); +bit_eq!(u16); +bit_eq!(u8); + +/// Macro for defining `PartiaEq` and `Eq` implementations on `BitMut`. +macro_rules! bit_mut_eq { + ($x:ty) => { + impl PartialEq for BitMut<'_, $x, P> { + #[doc = concat!(" + ``` + use bit_fields::BitMut; + let mut x = 5", stringify!($x), "; + let mut y = 5", stringify!($x), "; + let a = BitMut::<_,0>(&mut x); + let b = BitMut::<_,0>(&mut y); + + assert_eq!(a,b); + ``` + ")] + #[inline] + fn eq(&self, other: &Self) -> bool { + let a = bool::from(self); + let b = bool::from(other); + a == b + } + } + impl PartialEq for BitMut<'_, $x, P> { + #[doc = concat!(" + ``` + use bit_fields::BitMut; + let mut x = 5", stringify!($x), "; + assert_eq!(BitMut::<_,0>(&mut x),true); + assert_eq!(BitMut::<_,1>(&mut x),false); + assert_eq!(BitMut::<_,2>(&mut x),true); + ``` + ")] + #[inline] + fn eq(&self, other: &bool) -> bool { + bool::from(self) == *other + } + } + impl Eq for BitMut<'_, $x, P> {} + }; +} + +bit_mut_eq!(u128); +bit_mut_eq!(u64); +bit_mut_eq!(u32); +bit_mut_eq!(u16); +bit_mut_eq!(u8); diff --git a/src/bit-fields/src/bit_range.rs b/src/bit-fields/src/bit_range.rs new file mode 100644 index 00000000000..d957415b0b6 --- /dev/null +++ b/src/bit-fields/src/bit_range.rs @@ -0,0 +1,654 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/// A type interface for a range of bits. +#[derive(Debug, Clone)] +pub struct BitRange<'a, T, const START: u8, const END: u8>(pub &'a T); + +/// A type interface for a range of bits. +#[allow(clippy::module_name_repetitions)] +#[derive(Debug)] +pub struct BitRangeMut<'a, T, const START: u8, const END: u8>(pub &'a mut T); + +/// Macro for defining `impl Disply for BitRange`. +macro_rules! bit_range_display { + ($x:ty) => { + impl std::fmt::Display for BitRange<'_, $x, START, END> { + #[doc = concat!(" + ``` + use bit_fields::BitRange; + let x = 18", stringify!($x), "; + assert_eq!(BitRange::<_,0,4>(&x).to_string(),2", stringify!($x),".to_string()); + assert_eq!(BitRange::<_,4,8>(&x).to_string(),1", stringify!($x),".to_string()); + ``` + ")] + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.read()) + } + } + }; +} + +bit_range_display!(u128); +bit_range_display!(u64); +bit_range_display!(u32); +bit_range_display!(u16); +bit_range_display!(u8); + +/// Macro for defining `impl Disply for BitRangeMut`. +macro_rules! bit_range_mut_display { + ($x:ty) => { + impl std::fmt::Display for BitRangeMut<'_, $x, START, END> { + #[doc = concat!(" + ``` + use bit_fields::BitRangeMut; + let mut x = 18", stringify!($x), "; + assert_eq!(BitRangeMut::<_,0,4>(&mut x).to_string(),2", stringify!($x),".to_string()); + assert_eq!(BitRangeMut::<_,4,8>(&mut x).to_string(),1", stringify!($x),".to_string()); + ``` + ")] + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.read()) + } + } + }; +} + +bit_range_mut_display!(u128); +bit_range_mut_display!(u64); +bit_range_mut_display!(u32); +bit_range_mut_display!(u16); +bit_range_mut_display!(u8); + +/// Macro for defining `impl BitRange`. +macro_rules! bit_range { + ($x:ty, $mask:ident, $max:ident) => { + impl BitRange<'_, $x, START, END> { + pub const MASK: $x = $mask::(); + /// The maximum value this range can store + pub const MAX: $x = $max::(); + + #[doc = concat!(" + Returns the value of the bit range. + ``` + use bit_fields::BitRange; + let x = 18", stringify!($x), "; + assert_eq!(BitRange::<_,0,4>(&x),2); + assert_eq!(BitRange::<_,4,8>(&x),1); + ``` + ")] + #[must_use] + #[inline] + pub fn read(&self) -> $x { + <$x>::from(self) + } + + #[doc = concat!(" + Convenience alias for [`Self::MAX`]. + + ``` + use bit_fields::BitRange; + let x = ", stringify!($x), "::default(); + + assert_eq!(BitRange::<_,0,1>(&x).get_max(),1); + assert_eq!(BitRange::<_,0,2>(&x).get_max(),3); + assert_eq!(BitRange::<_,0,3>(&x).get_max(),7); + assert_eq!(BitRange::<_,0,4>(&x).get_max(),15); + assert_eq!(BitRange::<_,0,5>(&x).get_max(),31); + assert_eq!(BitRange::<_,0,6>(&x).get_max(),63); + assert_eq!(BitRange::<_,0,7>(&x).get_max(),127); + assert_eq!(BitRange::<_,0,8>(&x).get_max(),255); + ``` + ")] + #[must_use] + #[inline] + pub const fn get_max(&self) -> $x { + Self::MAX + } + #[doc = concat!(" + Convenience alias for [`Self::MASK`]. + + ``` + use bit_fields::BitRange; + let x = ", stringify!($x), "::default(); + + assert_eq!(BitRange::<_,0,1>(&x).get_mask(),1); + assert_eq!(BitRange::<_,1,3>(&x).get_mask(),6); + assert_eq!(BitRange::<_,3,7>(&x).get_mask(),120); + assert_eq!(BitRange::<_,7,8>(&x).get_mask(),128); + ``` + ")] + #[must_use] + #[inline] + pub const fn get_mask(&self) -> $x { + Self::MASK + } + } + }; +} + +bit_range!(u128, mask_u128, max_u128); +bit_range!(u64, mask_u64, max_u64); +bit_range!(u32, mask_u32, max_u32); +bit_range!(u16, mask_u16, max_u16); +bit_range!(u8, mask_u8, max_u8); + +/// Macro for defining `impl BitRangeMut`. +macro_rules! bit_mut_range { + ($x:ty, $mask:ident, $max:ident) => { + impl BitRangeMut<'_, $x, START, END> { + pub const MASK: $x = $mask::(); + /// The maximum value this range can store + pub const MAX: $x = $max::(); + + #[doc = concat!(" + Returns the value of the bit range. + ``` + use bit_fields::BitRangeMut; + let mut x = 18", stringify!($x), "; + assert_eq!(BitRangeMut::<_,0,4>(&mut x),2", stringify!($x),"); + assert_eq!(BitRangeMut::<_,4,8>(&mut x),1", stringify!($x),"); + ``` + ")] + #[must_use] + #[inline] + pub fn read(&self) -> $x { + <$x>::from(self) + } + + // `x <= Self::MAX` guarantees `x << START` is safe. + #[doc = concat!(" + Alias for [`Self::checked_assign`]. + + # Errors + + When `x` is greater than the maximum storable value in `self`. + ``` + use bit_fields::{BitRangeMut, CheckedAssignError}; + let mut x = 18", stringify!($x), "; + + let mut nibble = BitRangeMut::<_,0,4>(&mut x); + assert_eq!(nibble.write(16),Err(CheckedAssignError)); + assert_eq!(nibble.write(15),Ok(())); + + let mut nibble = BitRangeMut::<_,4,8>(&mut x); + assert_eq!(nibble.write(16),Err(CheckedAssignError)); + assert_eq!(nibble.write(15),Ok(())); + ``` + ")] + #[inline] + pub fn write(&mut self, x: $x) -> Result<(), $crate::CheckedAssignError> { + self.checked_assign(x) + } + + #[doc = concat!(" + Adds `x` to the value of the bit range. + + # Errors + + 1. When `x` is greater than the maximum value storable in the bit range. + 2. When adding `x` to the value of the bit range would overflow. + + ``` + use bit_fields::{BitRangeMut, CheckedAddAssignError}; + let mut x = 18", stringify!($x), "; + + let mut nibble = BitRangeMut::<_,0,4>(&mut x); + assert_eq!(nibble,2); + assert_eq!(nibble.checked_add_assign(16),Err(CheckedAddAssignError::OutOfRange)); + assert_eq!(nibble.checked_add_assign(14),Err(CheckedAddAssignError::Overflow)); + assert_eq!(nibble.checked_add_assign(2),Ok(())); + assert_eq!(nibble,4); + assert_eq!(x,20); + + let mut nibble = BitRangeMut::<_,4,8>(&mut x); + assert_eq!(nibble,1); + assert_eq!(nibble.checked_add_assign(16),Err(CheckedAddAssignError::OutOfRange)); + assert_eq!(nibble.checked_add_assign(15),Err(CheckedAddAssignError::Overflow)); + assert_eq!(nibble.checked_add_assign(2),Ok(())); + assert_eq!(nibble,3); + assert_eq!(x,52); + ``` + ")] + #[allow(clippy::integer_arithmetic, clippy::arithmetic_side_effects)] + #[inline] + pub fn checked_add_assign( + &mut self, + x: $x, + ) -> Result<(), $crate::CheckedAddAssignError> { + if x <= Self::MAX { + let cur = self.read(); + if x <= Self::MAX - cur { + debug_assert!(cur + x <= Self::MAX); + // SAFETY: `x <= Self::MAX - cur` implies `cur + x <= Self::MAX`. + unsafe { + self.unchecked_assign(cur + x) + } + Ok(()) + } else { + Err($crate::CheckedAddAssignError::Overflow) + } + } else { + Err($crate::CheckedAddAssignError::OutOfRange) + } + } + + // `x <= Self::MAX` guarantees `x << START` is safe and `x <= cur` guarantees + // `self.data_mut() -= shift` is safe. + #[doc = concat!(" + Subtract `x` from the value of the bit range. + + # Errors + + 1. When `x` is greater than the maximum value storable in the bit range. + 2. When subtracting `x` from the value of the bit range would underflow. + + ``` + use bit_fields::{BitRangeMut, CheckedSubAssignError}; + let mut x = 18", stringify!($x), "; + + let mut nibble = BitRangeMut::<_,0,4>(&mut x); + assert_eq!(nibble,2); + assert_eq!(nibble.checked_sub_assign(16),Err(CheckedSubAssignError::OutOfRange)); + assert_eq!(nibble.checked_sub_assign(3),Err(CheckedSubAssignError::Underflow)); + assert_eq!(nibble.checked_sub_assign(1),Ok(())); + assert_eq!(nibble,1); + assert_eq!(x,17); + + let mut nibble = BitRangeMut::<_,4,8>(&mut x); + assert_eq!(nibble,1); + assert_eq!(nibble.checked_sub_assign(16),Err(CheckedSubAssignError::OutOfRange)); + assert_eq!(nibble.checked_sub_assign(2),Err(CheckedSubAssignError::Underflow)); + assert_eq!(nibble.checked_sub_assign(1),Ok(())); + assert_eq!(nibble,0); + assert_eq!(x,1); + ``` + ")] + #[allow(clippy::integer_arithmetic, clippy::arithmetic_side_effects)] + #[inline] + pub fn checked_sub_assign( + &mut self, + x: $x, + ) -> Result<(), $crate::CheckedSubAssignError> { + if x <= Self::MAX { + let cur = self.read(); + if x <= cur { + // SAFETY: `x <= cur` implies `cur - x >= 0`. + unsafe { + self.unchecked_assign(cur - x); + } + Ok(()) + } else { + Err($crate::CheckedSubAssignError::Underflow) + } + } else { + Err($crate::CheckedSubAssignError::OutOfRange) + } + } + + // `x <= Self::MAX` guarantees `x << START` is safe. + #[doc = concat!(" + Sets the bit range returning `Err(())` when the given `x` is not storable in the + range. + + # Errors + + When `x` is greater than the maximum storable value in `self`. + ``` + use bit_fields::{BitRangeMut, CheckedAssignError}; + let mut x = 18", stringify!($x), "; + + let mut nibble = BitRangeMut::<_,0,4>(&mut x); + assert_eq!(nibble.checked_assign(16),Err(CheckedAssignError)); + assert_eq!(nibble.checked_assign(15),Ok(())); + assert_eq!(x,31); + + let mut nibble = BitRangeMut::<_,4,8>(&mut x); + assert_eq!(nibble.checked_assign(16),Err(CheckedAssignError)); + assert_eq!(nibble.checked_assign(15),Ok(())); + assert_eq!(x,255); + ``` + ")] + #[allow(clippy::integer_arithmetic, clippy::arithmetic_side_effects)] + #[inline] + pub fn checked_assign(&mut self, x: $x) -> Result<(), $crate::CheckedAssignError> { + if x <= Self::MAX { + // SAFETY: Safe due to checking `x <= Self::MAX`. + unsafe { + self.unchecked_assign(x); + } + Ok(()) + } else { + Err($crate::CheckedAssignError) + } + } + #[doc=" + Sets the bit range. + + # Panics + + In debug when `x > Self::MAX`. + "] + #[allow(clippy::integer_arithmetic, clippy::arithmetic_side_effects)] + #[inline] + pub unsafe fn unchecked_assign(&mut self, x: $x) { + debug_assert!(x <= Self::MAX); + let shift = x << START; + *self.0 = shift | (*self.0 & !Self::MASK); + } + #[doc = concat!(" + Convenience alias for [`Self::MAX`]. + + ``` + use bit_fields::BitRangeMut; + let mut x = ", stringify!($x), "::default(); + + assert_eq!(BitRangeMut::<_,0,1>(&mut x).get_max(),1); + assert_eq!(BitRangeMut::<_,0,2>(&mut x).get_max(),3); + assert_eq!(BitRangeMut::<_,0,3>(&mut x).get_max(),7); + assert_eq!(BitRangeMut::<_,0,4>(&mut x).get_max(),15); + assert_eq!(BitRangeMut::<_,0,5>(&mut x).get_max(),31); + assert_eq!(BitRangeMut::<_,0,6>(&mut x).get_max(),63); + assert_eq!(BitRangeMut::<_,0,7>(&mut x).get_max(),127); + assert_eq!(BitRangeMut::<_,0,8>(&mut x).get_max(),255); + ``` + ")] + #[must_use] + #[inline] + pub const fn get_max(&self) -> $x { + Self::MAX + } + #[doc = concat!(" + Convenience alias for [`Self::MASK`]. + + ``` + use bit_fields::BitRangeMut; + let mut x = ", stringify!($x), "::default(); + + assert_eq!(BitRangeMut::<_,0,1>(&mut x).get_mask(),1); + assert_eq!(BitRangeMut::<_,1,3>(&mut x).get_mask(),6); + assert_eq!(BitRangeMut::<_,3,7>(&mut x).get_mask(),120); + assert_eq!(BitRangeMut::<_,7,8>(&mut x).get_mask(),128); + ``` + ")] + #[must_use] + #[inline] + pub const fn get_mask(&self) -> $x { + Self::MASK + } + } + }; +} + +bit_mut_range!(u128, mask_u128, max_u128); +bit_mut_range!(u64, mask_u64, max_u64); +bit_mut_range!(u32, mask_u32, max_u32); +bit_mut_range!(u16, mask_u16, max_u16); +bit_mut_range!(u8, mask_u8, max_u8); + +/// Macro for defining `From` implementations on `BitRange` and `BitRangeMut`. +macro_rules! bit_range_from { + ($x:ty, $bit_range: ident) => { + // `START < 8 * size_of::<$x>()` is always true so the right shift will not panic. + #[allow(clippy::integer_arithmetic, clippy::arithmetic_side_effects)] + impl From<&$bit_range<'_, $x, START, END>> for $x { + #[inline] + fn from(this: &$bit_range<'_, $x, START, END>) -> Self { + let a = $bit_range::<'_, $x, START, END>::MASK & *this.0; + a >> START + } + } + }; +} + +bit_range_from!(u128, BitRange); +bit_range_from!(u64, BitRange); +bit_range_from!(u32, BitRange); +bit_range_from!(u16, BitRange); +bit_range_from!(u8, BitRange); + +bit_range_from!(u128, BitRangeMut); +bit_range_from!(u64, BitRangeMut); +bit_range_from!(u32, BitRangeMut); +bit_range_from!(u16, BitRangeMut); +bit_range_from!(u8, BitRangeMut); + +/// Macro for defining `PartialEq` and `Eq` implementations on `BitRange` and `BitRangeMut`. +macro_rules! bit_range_eq { + ($x:ty, $bit_range: ident) => { + impl PartialEq<$x> for $bit_range<'_, $x, START, END> { + #[inline] + fn eq(&self, other: &$x) -> bool { + self.read() == *other + } + } + impl PartialEq for $bit_range<'_, $x, START, END> { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.read() == other.read() + } + } + impl Eq for $bit_range<'_, $x, START, END> {} + }; +} + +bit_range_eq!(u128, BitRange); +bit_range_eq!(u64, BitRange); +bit_range_eq!(u32, BitRange); +bit_range_eq!(u16, BitRange); +bit_range_eq!(u8, BitRange); + +bit_range_eq!(u128, BitRangeMut); +bit_range_eq!(u64, BitRangeMut); +bit_range_eq!(u32, BitRangeMut); +bit_range_eq!(u16, BitRangeMut); +bit_range_eq!(u8, BitRangeMut); + +/// Macro for defining `PartialOrd` and `Ord` implementations on `BitRange` and `BitRangeMut`. +macro_rules! bit_range_ord { + ($x:ty, $bit_range: ident) => { + impl PartialOrd<$x> for $bit_range<'_, $x, START, END> { + #[inline] + fn partial_cmp(&self, other: &$x) -> Option { + Some(self.read().cmp(&other)) + } + } + impl PartialOrd for $bit_range<'_, $x, START, END> { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.read().cmp(&other.read())) + } + } + impl Ord for $bit_range<'_, $x, START, END> { + #[inline] + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.read().cmp(&other.read()) + } + } + }; +} + +bit_range_ord!(u128, BitRange); +bit_range_ord!(u64, BitRange); +bit_range_ord!(u32, BitRange); +bit_range_ord!(u16, BitRange); +bit_range_ord!(u8, BitRange); + +bit_range_ord!(u128, BitRangeMut); +bit_range_ord!(u64, BitRangeMut); +bit_range_ord!(u32, BitRangeMut); +bit_range_ord!(u16, BitRangeMut); +bit_range_ord!(u8, BitRangeMut); + +/// Returns a value where in the binary representation all bits to the right of the x'th bit from +/// the left are 1. +macro_rules! shift { + ($x:ident, $max:expr, $ty:path) => {{ + // These values are only evaluated at compile-time, thus a failure can only occur at + // compile-time and would be immediately obvious. Thus it is safe to use arithmetic here. + #[allow(clippy::integer_arithmetic)] + if $x == 0 { + 0 + } else if $x < $max { + (1 << $x) - 1 + } else if $x == $max { + $ty + } else { + // TODO Use `unreachable!()` here when panicking in const context is stabilized. + 0 + } + }}; +} + +/// Macro for defining mask functions. +macro_rules! mask_fn { + ($f:ident, $x:ty, $y:path) => { + /// Returns mask over range. + /// + /// We take `START`and `END` as const generics to ensure compile-time + /// evaluation. + // These values are only evaluated at compile-time, thus a failure can only occur at + // compile-time. This makes most fallible operations safe. + #[allow( + clippy::as_conversions, + clippy::arithmetic_side_effects, + clippy::integer_arithmetic + )] + #[must_use] + #[inline] + pub const fn $f() -> $x { + assert!(END >= START); + let size = 8 * std::mem::size_of::<$x>(); + assert!(END as usize <= size); + + let front = shift!(START, size as u8, $y); + let back = shift!(END, size as u8, $y); + !front & back + } + }; +} +mask_fn!(mask_u128, u128, u128::MAX); +mask_fn!(mask_u64, u64, u64::MAX); +mask_fn!(mask_u32, u32, u32::MAX); +mask_fn!(mask_u16, u16, u16::MAX); +mask_fn!(mask_u8, u8, u8::MAX); + +/// Macro for defining max functions. +macro_rules! max_fn { + ($f:ident, $x:ty) => { + /// Returns maximum value storable in a range. + /// + /// We take `START`and `END` as const generics to ensure compile-time + /// evaluation. + // These values are only evaluated at compile-time, thus a failure can only occur at + // compile-time. This makes most fallible operations safe. + #[allow( + clippy::integer_arithmetic, + clippy::as_conversions, + clippy::arithmetic_side_effects + )] + #[must_use] + #[inline] + pub const fn $f() -> $x { + assert!(END >= START); + assert!(END as usize <= 8 * std::mem::size_of::<$x>()); + + match (2 as $x).overflowing_pow((END - START) as u32) { + (_, true) => <$x>::MAX, + (max, false) => max - 1, + } + } + }; +} +max_fn!(max_u128, u128); +max_fn!(max_u64, u64); +max_fn!(max_u32, u32); +max_fn!(max_u16, u16); +max_fn!(max_u8, u8); + +#[cfg(test)] +mod tests { + #![allow( + non_snake_case, + clippy::dbg_macro, + clippy::unwrap_used, + clippy::as_conversions, + clippy::shadow_unrelated + )] + + use super::*; + + // Testing masks work + #[test] + fn mask() { + // These top level checks exists to communicate to code coverage that this function is + // covered + assert_eq!(mask_u128::<0, 0>(), 0); + assert_eq!(mask_u64::<0, 0>(), 0); + assert_eq!(mask_u32::<0, 0>(), 0); + assert_eq!(mask_u16::<0, 0>(), 0); + assert_eq!(mask_u8::<0, 0>(), 0); + // u128 + assert_eq!( + mask_u128::<0, 128>(), + 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff + ); + assert_eq!( + mask_u128::<0, 64>(), + 0x0000_0000_0000_0000_ffff_ffff_ffff_ffff + ); + assert_eq!( + mask_u128::<64, 128>(), + 0xffff_ffff_ffff_ffff_0000_0000_0000_0000 + ); + // u64 + assert_eq!(mask_u64::<0, 64>(), 0xffff_ffff_ffff_ffff); + assert_eq!(mask_u64::<0, 32>(), 0x0000_0000_ffff_ffff); + assert_eq!(mask_u64::<32, 64>(), 0xffff_ffff_0000_0000); + assert_eq!(mask_u64::<4, 60>(), 0x0fff_ffff_ffff_fff0); + assert_eq!(mask_u64::<8, 56>(), 0x00ff_ffff_ffff_ff00); + assert_eq!(mask_u64::<12, 52>(), 0x000f_ffff_ffff_f000); + assert_eq!(mask_u64::<16, 48>(), 0x0000_ffff_ffff_0000); + assert_eq!(mask_u64::<20, 44>(), 0x0000_0fff_fff0_0000); + assert_eq!(mask_u64::<24, 40>(), 0x0000_00ff_ff00_0000); + assert_eq!(mask_u64::<28, 36>(), 0x0000_000f_f000_0000); + assert_eq!( + mask_u64::<30, 34>(), + 0b0000_0000_0000_0000_0000_0000_0000_0011_1100_0000_0000_0000_0000_0000_0000_0000 + ); + assert_eq!( + mask_u64::<31, 33>(), + 0b0000_0000_0000_0000_0000_0000_0000_0001_1000_0000_0000_0000_0000_0000_0000_0000 + ); + // u32 + assert_eq!(mask_u32::<0, 32>(), 0xffff_ffff); + assert_eq!(mask_u32::<0, 16>(), 0x0000_ffff); + assert_eq!(mask_u32::<16, 32>(), 0xffff_0000); + assert_eq!(mask_u32::<4, 28>(), 0x0fff_fff0); + assert_eq!(mask_u32::<8, 24>(), 0x00ff_ff00); + assert_eq!(mask_u32::<12, 20>(), 0x000f_f000); + assert_eq!( + mask_u32::<14, 18>(), + 0b0000_0000_0000_0011_1100_0000_0000_0000 + ); + assert_eq!( + mask_u32::<15, 17>(), + 0b0000_0000_0000_0001_1000_0000_0000_0000 + ); + // u16 + assert_eq!(mask_u16::<0, 16>(), 0xffff); + assert_eq!(mask_u16::<0, 8>(), 0x00ff); + assert_eq!(mask_u16::<8, 16>(), 0xff00); + assert_eq!(mask_u16::<4, 12>(), 0x0ff0); + assert_eq!(mask_u16::<6, 10>(), 0b0000_0011_1100_0000); + assert_eq!(mask_u16::<7, 9>(), 0b0000_0001_1000_0000); + // u8 + assert_eq!(mask_u8::<0, 8>(), 0b1111_1111); + assert_eq!(mask_u8::<0, 4>(), 0b0000_1111); + assert_eq!(mask_u8::<4, 8>(), 0b1111_0000); + assert_eq!(mask_u8::<2, 6>(), 0b0011_1100); + assert_eq!(mask_u8::<3, 5>(), 0b0001_1000); + } +} diff --git a/src/bit-fields/src/errors.rs b/src/bit-fields/src/errors.rs new file mode 100644 index 00000000000..7aff8b8fffe --- /dev/null +++ b/src/bit-fields/src/errors.rs @@ -0,0 +1,66 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/// Error type for `impl std::convert::TryFrom> +/// for YourBitField`. +#[derive(Debug, thiserror::Error)] +#[error("Feature flag given in set which is not defined in bit field.")] +pub struct TryFromFlagSetError; + +/// Error type for `impl +/// std::convert::TryFrom> for YourBitField`. +#[derive(Debug, thiserror::Error)] +pub enum TryFromFieldMapError { + /// Bit range given in map which is not defined in bit field. + #[error("Bit range given in map which is not defined in bit field.")] + UnknownRange, + /// Failed to assign value from field map. + #[error("Failed to assign value from field map: {0}")] + CheckedAssign(#[from] CheckedAssignError), +} + +/// Error type for `impl +/// std::convert::TryFrom<(std::collections::HashSet,std::collections::HashMap)> +/// for YourBitField`. +#[derive(Debug, thiserror::Error)] +pub enum TryFromFlagSetAndFieldMapError { + /// Failed to parse flag set. + #[error("Feature flag given in set which is not defined in bit field.")] + MissingFlag, + /// Bit range given in map which is not defined in bit field. + #[error("Bit range given in map which is not defined in bit field.")] + UnknownRange, + /// Failed to assign value from field map. + #[error("Failed to assign value from field map: {0}")] + CheckedAssign(#[from] CheckedAssignError), +} + +/// Error type for [`crate::BitRangeMut::checked_add_assign()`], [`crate::BitRangeMut::checked_add_assign()`], etc. +#[derive(Debug, PartialEq, Eq, thiserror::Error)] +pub enum CheckedAddAssignError { + /// Operation would result in overflow of bit range. + #[error("Operation would result in overflow of bit range.")] + Overflow, + /// Given value is more than maximum value storable in bit range. + #[error("Given value is more than maximum value storable in bit range.")] + OutOfRange, +} + +/// Error type for [`crate::BitRangeMut::checked_sub_assign()`], [`crate::BitRangeMut::checked_sub_assign()`], etc. +#[derive(Debug, PartialEq, Eq, thiserror::Error)] +pub enum CheckedSubAssignError { + /// Operation would result in underflow of bit range. + #[error("Operation would result in underflow of bit range.")] + Underflow, + /// Given value is more than maximum value storable in bit range. + #[error("Given value is more than maximum value storable in bit range.")] + OutOfRange, +} + +/// Error type for [`crate::BitRangeMut::checked_assign()`], [`crate::BitRangeMut::checked_assign()`], etc. +#[derive(Debug, PartialEq, Eq, thiserror::Error)] +#[error("Given value is greater than maximum storable value in bit range.")] +pub struct CheckedAssignError; diff --git a/src/bit-fields/src/example.rs b/src/bit-fields/src/example.rs new file mode 100644 index 00000000000..1478815ac5a --- /dev/null +++ b/src/bit-fields/src/example.rs @@ -0,0 +1,50 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +extern crate self as bit_fields; + +bit_fields::bitfield!(ExampleBitFieldU32,u32,{ + /// RANGE1 bit field + RANGE1: 0..1, + /// SSE bit flag + SSE: 2, + /// SSE1 bit flag + SSE1: 3, + /// RANGE2 bit field + RANGE2: 4..6, + /// SSE2 bit flag + SSE2: 9, + /// SSE3 bit flag + SSE3: 10, + /// RANGE3 bit field + RANGE3: 12..15, + /// SSE4 bit flag + SSE4: 18, +}); + +bit_fields::bitfield!(BitFieldIndexedU16, u16, { + #[skip] + one: 0..1, + one0: one[0..1], + #[skip] + one00: one0[0], + #[skip] + two: 1..3, + two0: two[0..1], + #[skip] + two00: two0[0], + two1: two[1], + #[skip] + three: 3..6, + #[skip] + three0: three[0..1], + three00: three0[0], + three1: three[1..3], + #[skip] + three10: three1[0..1], + #[skip] + three11: three1[1], + four: 6..10, + five: 10..15, + six: 15 +}); diff --git a/src/bit-fields/src/lib.rs b/src/bit-fields/src/lib.rs new file mode 100644 index 00000000000..b8d802b49e6 --- /dev/null +++ b/src/bit-fields/src/lib.rs @@ -0,0 +1,838 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +#![warn(clippy::pedantic, clippy::restriction)] +#![allow( + clippy::blanket_clippy_restriction_lints, + clippy::implicit_return, + clippy::pattern_type_mismatch, + clippy::std_instead_of_alloc, + clippy::std_instead_of_core, + clippy::pub_use, + clippy::non_ascii_literal, + clippy::single_char_lifetime_names, + clippy::exhaustive_enums, + clippy::exhaustive_structs, + clippy::unseparated_literal_suffix, + clippy::mod_module_files +)] + +//! A macro to generate structures which support bit flags and sub-bytes ranges. +//! +//! A [bitflags](https://crates.io/crates/bitflags) like library which also supports value ranges. +//! +//! Originally designed to support efficiently interacting with [CPUID](https://en.wikipedia.org/wiki/CPUID). +//! +//! See [`example::ExampleBitFieldU32`] for an example. + +/// Example bit fields. +#[cfg(doc)] +pub mod example; + +pub use bit_fields_macros::*; + +/// Bit flag types. +mod bit; +pub use bit::*; + +/// Bit range types. +mod bit_range; +pub use bit_range::*; + +/// Exported error types. +mod errors; +pub use errors::*; + +/// Trait used for indexing into a bit field. +/// +/// ```ignore +/// let bit_field = bit_fields::bitfield!(MyBitField,u8,{A:0,B:1,C:3,D:7}); +/// let _a = bit_field.bit::<0>(); +/// let _b = bit_field.bit::<1>(); +/// let _c = bit_field.bit::<3>(); +/// +/// // You can index to unnamed bits like this. +/// let _ = bit_field.bit::<4>(); +/// let _ = bit_field.bit::<5>(); +/// let sixth = bit_field.bit::<6>(); +/// println!("sixth: {}",sixth); +/// +/// let d = bit_field.bit::<7>(); +/// println!("d: {}",d); +/// ``` +pub trait BitIndex { + /// Gets a reference to a bit. + fn bit(&self) -> Bit<'_, T, P>; +} +/// Trait used for mutable indexing into a bit field. +/// +/// ```ignore +/// let bit_field = bit_fields::bitfield!(MyBitField,u8,{A:0,B:1,C:3,D:7}); +/// let _a = bit_field.bit_mut::<0>(); +/// let _b = bit_field.bit_mut::<1>(); +/// let _c = bit_field.bit_mut::<3>(); +/// +/// // You can index to unnamed bits like this. +/// let _ = bit_field.bit_mut::<4>(); +/// let _ = bit_field.bit_mut::<5>(); +/// +/// // We set the 6th bit to 0. +/// let mut sixth = bit_field.bit_mut::<6>(); +/// sixth.off(); +/// +/// // We set the 7th bit to 1. +/// let mut d = bit_field.bit_mut::<7>(); +/// d.on(); +/// ``` +pub trait BitIndexMut { + /// Gets a mutable reference to a bit. + fn bit_mut(&mut self) -> BitMut<'_, T, P>; +} +/// Trait defining function that returns if all defined bits are equal, ignoring undefined bits. +pub trait Equal { + /// Returns if all defined bits are equal, ignoring undefined bits. + fn equal(&self, other: &Self) -> bool; +} +impl Equal for &T { + #[inline] + fn equal(&self, other: &Self) -> bool { + (**self).equal(other) + } +} +impl Equal for &mut T { + #[inline] + fn equal(&self, other: &Self) -> bool { + (**self).equal(other) + } +} +/// Convenience macro for defining `Equal` implementations on primitives. +macro_rules! impl_equal { + ($t:ty) => { + impl Equal for $t { + #[inline] + fn equal(&self, other: &Self) -> bool { + self == other + } + } + }; +} +impl_equal!(usize); +impl_equal!(u128); +impl_equal!(u64); +impl_equal!(u32); +impl_equal!(u16); +impl_equal!(u8); +impl_equal!(isize); +impl_equal!(i128); +impl_equal!(i64); +impl_equal!(i32); +impl_equal!(i16); +impl_equal!(i8); + +impl Equal for [T; N] { + #[inline] + fn equal(&self, other: &Self) -> bool { + self.iter().zip(other.iter()).all(|(a, b)| a.equal(b)) + } +} +impl Equal for [T] { + #[inline] + fn equal(&self, other: &Self) -> bool { + self.len() == other.len() && self.iter().zip(other.iter()).all(|(a, b)| a.equal(b)) + } +} +impl Equal for Option { + #[inline] + fn equal(&self, other: &Self) -> bool { + match (self.as_ref(), other.as_ref()) { + (Some(_), None) | (None, Some(_)) => false, + (None, None) => true, + (Some(a), Some(b)) => a.equal(b), + } + } +} + +#[cfg(test)] +mod tests { + #![allow( + non_snake_case, + clippy::dbg_macro, + clippy::unwrap_used, + clippy::as_conversions, + clippy::shadow_unrelated, + clippy::decimal_literal_representation + )] + use std::convert::TryFrom; + + use rand::Rng; + + use super::*; + use crate as bit_fields; + + bitfield!(BitFieldIndexedU16, u16, { + #[skip] + one: 0..1, + one0: one[0..1], + #[skip] + one00: one0[0], + #[skip] + two: 1..3, + two0: two[0..1], + #[skip] + two00: two0[0], + two1: two[1], + #[skip] + three: 3..6, + #[skip] + three0: three[0..1], + three00: three0[0], + three1: three[1..3], + #[skip] + three10: three1[0..1], + #[skip] + three11: three1[1], + four: 6..10, + five: 10..15, + six: 15 + }); + + bitfield!(BitFieldu128, u128, { + one: 0..1, + two: 1..3, + three: 3..6, + four: 6..10, + five: 10..15, + six: 15..21, + seven: 21..28, + eight: 28..36, + nine: 36..45, + ten: 45..55, + eleven: 55..66, + twelve: 66..78, + thirteen: 78..91, + fourteen: 91..105, + fifteen: 105..120, + sixteen: 120, + seventeen: 121, + eighteen: 122, + nineteen: 123, + twenty: 124, + twentyone: 125, + twentytwo: 126, + twentythree: 127, + }); + bitfield!(BitFieldu64, u64, { + one: 0..1, + two: 1..3, + three: 3..6, + four: 6..10, + five: 10..15, + six: 15..21, + seven: 21..28, + eight: 28..36, + nine: 36..45, + ten: 45..55, + eleven: 55, + twelve: 56, + thirteen: 57, + fourteen: 58, + fifteen: 59, + sixteen: 60, + seventeen: 61, + eighteen: 62, + nineteen: 63 + }); + bitfield!(BitFieldu32, u32, { + one: 0..1, + two: 1..3, + three: 3..6, + four: 6..10, + five: 10..15, + six: 15..21, + seven: 21..28, + eight: 28, + nine: 29, + ten: 30, + eleven: 31 + }); + bitfield!(BitFieldu16, u16, { + one: 0..1, + two: 1..3, + three: 3..6, + four: 6..10, + five: 10..15, + six: 15 + }); + bitfield!(BitFieldu8, u8, { + one: 0..1, + two: 1..3, + three: 3..6, + four: 6, + five: 7 + }); + + bitfield!( + GeneratedBitField, + u32, + { + RANGE1: + 0..1, + SSE: + 2, + SSE1: + 3, + RANGE2: + 4..6, + SSE2: + 9, + SSE3: + 10, + RANGE3: + 12..15, + SSE4: + 18 + } + ); + + const ITERATIONS: usize = 100_000; + const MAX: u32 = 100; + + #[allow(clippy::trivially_copy_pass_by_ref)] + fn check(bitfield: &GeneratedBitField, r1: u32, r2: u32, r3: u32) { + assert_eq!(bitfield.RANGE1(), r1); + assert_eq!(bitfield.SSE(), true); + assert_eq!(bitfield.SSE1(), true); + assert_eq!(bitfield.RANGE2(), r2); + assert_eq!(bitfield.SSE2(), true); + assert_eq!(bitfield.SSE3(), false); + assert_eq!(bitfield.RANGE3(), r3); + assert_eq!(bitfield.SSE4(), false); + } + + #[test] + fn indexed() { + let bit_field = BitFieldIndexedU16(0b1010_1110_0101_0111); + + assert_eq!(bit_field.one().read(), 1); + assert_eq!(bit_field.one0().read(), 1); + assert!(bit_field.one00().is_on()); + + assert_eq!(bit_field.two().read(), 3); + assert_eq!(bit_field.two0().read(), 1); + assert!(bit_field.two00().is_on()); + assert!(bit_field.two1().is_on()); + + assert_eq!(bit_field.three().read(), 2); + assert_eq!(bit_field.three0().read(), 0); + assert!(bit_field.three00().is_off()); + assert_eq!(bit_field.three1().read(), 1); + assert_eq!(bit_field.three10().read(), 1); + assert!(bit_field.three11().is_off()); + + assert_eq!(bit_field.four().read(), 9); + assert_eq!(bit_field.five().read(), 11); + assert!(bit_field.six().is_on()); + } + + #[test] + fn display() { + let field_u16 = BitFieldu16(0b1010_1110_0101_0111); + let field_u8 = BitFieldu8(0b1010_1110); + #[rustfmt::skip] + assert_eq!(field_u16.to_string(),"\ + ┌───────┬─────────────┬─────────────┬─────────────┬─────────────┬─────────────┬───────┐\n\ + │ \x1b[1mBit/s\x1b[0m │ 00..01 │ 01..03 │ 03..06 │ 06..10 │ 10..15 │ 15 │\n\ + ├───────┼─────────────┼─────────────┼─────────────┼─────────────┼─────────────┼───────┤\n\ + │ \x1b[1mDesc\x1b[0m │ one │ two │ three │ four │ five │ six │\n\ + ├───────┼─────────────┼─────────────┼─────────────┼─────────────┼─────────────┼───────┤\n\ + │ \x1b[1mValue\x1b[0m │ 1 │ 3 │ 2 │ 9 │ 11 │ true │\n\ + └───────┴─────────────┴─────────────┴─────────────┴─────────────┴─────────────┴───────┘\n\ + "); + #[rustfmt::skip] + assert_eq!(field_u8.to_string(),"\ + ┌───────┬─────────────┬─────────────┬─────────────┬───────┬───────┐\n\ + │ \x1b[1mBit/s\x1b[0m │ 00..01 │ 01..03 │ 03..06 │ 06 │ 07 │\n\ + ├───────┼─────────────┼─────────────┼─────────────┼───────┼───────┤\n\ + │ \x1b[1mDesc\x1b[0m │ one │ two │ three │ four │ five │\n\ + ├───────┼─────────────┼─────────────┼─────────────┼───────┼───────┤\n\ + │ \x1b[1mValue\x1b[0m │ 0 │ 3 │ 5 │ false │ true │\n\ + └───────┴─────────────┴─────────────┴─────────────┴───────┴───────┘\n\ + "); + } + // Testing bit field sizes + #[test] + fn size() { + use std::mem::size_of; + assert_eq!(size_of::(), size_of::()); + assert_eq!(size_of::(), size_of::()); + assert_eq!(size_of::(), size_of::()); + assert_eq!(size_of::(), size_of::()); + assert_eq!(size_of::(), size_of::()); + } + #[test] + fn type_max() { + // u128 + assert_eq!(BitRange::::MAX, 1); + assert_eq!(BitRange::::MAX, 3); + assert_eq!(BitRange::::MAX, 7); + assert_eq!(BitRange::::MAX, 15); + assert_eq!(BitRange::::MAX, 31); + assert_eq!(BitRange::::MAX, 63); + assert_eq!(BitRange::::MAX, 127); + assert_eq!(BitRange::::MAX, 255); + assert_eq!(BitRange::::MAX, 511); + assert_eq!(BitRange::::MAX, 1023); + assert_eq!(BitRange::::MAX, 2047); + assert_eq!(BitRange::::MAX, 4095); + assert_eq!(BitRange::::MAX, 8191); + assert_eq!(BitRange::::MAX, 16383); + assert_eq!(BitRange::::MAX, 32767); + + // u64 + assert_eq!(BitRange::::MAX, 1); + assert_eq!(BitRange::::MAX, 3); + assert_eq!(BitRange::::MAX, 7); + assert_eq!(BitRange::::MAX, 15); + assert_eq!(BitRange::::MAX, 31); + assert_eq!(BitRange::::MAX, 63); + assert_eq!(BitRange::::MAX, 127); + assert_eq!(BitRange::::MAX, 255); + assert_eq!(BitRange::::MAX, 511); + assert_eq!(BitRange::::MAX, 1023); + + // u32 + assert_eq!(BitRange::::MAX, 1); + assert_eq!(BitRange::::MAX, 3); + assert_eq!(BitRange::::MAX, 7); + assert_eq!(BitRange::::MAX, 15); + assert_eq!(BitRange::::MAX, 31); + assert_eq!(BitRange::::MAX, 63); + assert_eq!(BitRange::::MAX, 127); + + // u16 + assert_eq!(BitRange::::MAX, 1); + assert_eq!(BitRange::::MAX, 3); + assert_eq!(BitRange::::MAX, 7); + assert_eq!(BitRange::::MAX, 15); + assert_eq!(BitRange::::MAX, 31); + + assert_eq!(BitRange::::MAX, 65535); + assert_eq!(BitRange::::MAX, 255); + assert_eq!(BitRange::::MAX, 255); + assert_eq!(BitRange::::MAX, 255); + assert_eq!(BitRange::::MAX, 15); + assert_eq!(BitRange::::MAX, 3); + + // u8 + assert_eq!(BitRange::::MAX, 1); + assert_eq!(BitRange::::MAX, 3); + assert_eq!(BitRange::::MAX, 7); + + assert_eq!(BitRange::::MAX, 255); + assert_eq!(BitRange::::MAX, 15); + assert_eq!(BitRange::::MAX, 15); + assert_eq!(BitRange::::MAX, 15); + assert_eq!(BitRange::::MAX, 3); + } + #[test] + fn value_max() { + let field_u128: BitFieldu128 = BitFieldu128(0); + let field_u64: BitFieldu64 = BitFieldu64(0); + let field_u32: BitFieldu32 = BitFieldu32(0); + let field_u16: BitFieldu16 = BitFieldu16(0); + let field_u8: BitFieldu8 = BitFieldu8(0); + + // u128 + assert_eq!(field_u128.one().get_max(), 1); + assert_eq!(field_u128.two().get_max(), 3); + assert_eq!(field_u128.three().get_max(), 7); + assert_eq!(field_u128.four().get_max(), 15); + assert_eq!(field_u128.five().get_max(), 31); + assert_eq!(field_u128.six().get_max(), 63); + assert_eq!(field_u128.seven().get_max(), 127); + assert_eq!(field_u128.eight().get_max(), 255); + assert_eq!(field_u128.nine().get_max(), 511); + assert_eq!(field_u128.ten().get_max(), 1023); + assert_eq!(field_u128.eleven().get_max(), 2047); + assert_eq!(field_u128.twelve().get_max(), 4095); + assert_eq!(field_u128.thirteen().get_max(), 8191); + assert_eq!(field_u128.fourteen().get_max(), 16383); + assert_eq!(field_u128.fifteen().get_max(), 32767); + // u64 + assert_eq!(field_u64.one().get_max(), 1); + assert_eq!(field_u64.two().get_max(), 3); + assert_eq!(field_u64.three().get_max(), 7); + assert_eq!(field_u64.four().get_max(), 15); + assert_eq!(field_u64.five().get_max(), 31); + assert_eq!(field_u64.six().get_max(), 63); + assert_eq!(field_u64.seven().get_max(), 127); + assert_eq!(field_u64.eight().get_max(), 255); + assert_eq!(field_u64.nine().get_max(), 511); + assert_eq!(field_u64.ten().get_max(), 1023); + // u32 + assert_eq!(field_u32.one().get_max(), 1); + assert_eq!(field_u32.two().get_max(), 3); + assert_eq!(field_u32.three().get_max(), 7); + assert_eq!(field_u32.four().get_max(), 15); + assert_eq!(field_u32.five().get_max(), 31); + assert_eq!(field_u32.six().get_max(), 63); + assert_eq!(field_u32.seven().get_max(), 127); + // u16 + assert_eq!(field_u16.one().get_max(), 1); + assert_eq!(field_u16.two().get_max(), 3); + assert_eq!(field_u16.three().get_max(), 7); + assert_eq!(field_u16.four().get_max(), 15); + assert_eq!(field_u16.five().get_max(), 31); + // u8 + assert_eq!(field_u8.one().get_max(), 1); + assert_eq!(field_u8.two().get_max(), 3); + assert_eq!(field_u8.three().get_max(), 7); + } + #[test] + fn access() { + let bitfield = GeneratedBitField::from(23548); + assert_eq!(bitfield.RANGE1(), 0); + assert_eq!(bitfield.SSE(), true); + assert_eq!(bitfield.SSE1(), true); + assert_eq!(bitfield.RANGE2(), 3); + assert_eq!(bitfield.SSE2(), true); + assert_eq!(bitfield.SSE3(), false); + assert_eq!(bitfield.RANGE3(), 5); + assert_eq!(bitfield.SSE4(), false); + } + #[test] + fn flip() { + let mut bitfield = GeneratedBitField::from(23548); + assert_eq!(bitfield.RANGE1(), 0); + assert_eq!(bitfield.SSE(), true); + assert_eq!(bitfield.SSE1(), true); + assert_eq!(bitfield.RANGE2(), 3); + assert_eq!(bitfield.SSE2(), true); + assert_eq!(bitfield.SSE3(), false); + assert_eq!(bitfield.RANGE3(), 5); + assert_eq!(bitfield.SSE4(), false); + + bitfield.SSE_mut().flip(); + bitfield.SSE1_mut().flip(); + bitfield.SSE2_mut().flip(); + bitfield.SSE3_mut().flip(); + bitfield.SSE4_mut().flip(); + + assert_eq!(bitfield.RANGE1(), 0); + assert_eq!(bitfield.SSE(), false); + assert_eq!(bitfield.SSE1(), false); + assert_eq!(bitfield.RANGE2(), 3); + assert_eq!(bitfield.SSE2(), false); + assert_eq!(bitfield.SSE3(), true); + assert_eq!(bitfield.RANGE3(), 5); + assert_eq!(bitfield.SSE4(), true); + } + #[test] + fn set() { + let mut bitfield = GeneratedBitField::from(23548); + assert_eq!(bitfield.RANGE1(), 0); + assert_eq!(bitfield.SSE(), true); + assert_eq!(bitfield.SSE1(), true); + assert_eq!(bitfield.RANGE2(), 3); + assert_eq!(bitfield.SSE2(), true); + assert_eq!(bitfield.SSE3(), false); + assert_eq!(bitfield.RANGE3(), 5); + assert_eq!(bitfield.SSE4(), false); + + bitfield.SSE_mut().set(false); + bitfield.SSE1_mut().set(false); + bitfield.SSE2_mut().set(true); + bitfield.SSE3_mut().set(true); + bitfield.SSE4_mut().set(false); + + assert_eq!(bitfield.RANGE1(), 0); + assert_eq!(bitfield.SSE(), false); + assert_eq!(bitfield.SSE1(), false); + assert_eq!(bitfield.RANGE2(), 3); + assert_eq!(bitfield.SSE2(), true); + assert_eq!(bitfield.SSE3(), true); + assert_eq!(bitfield.RANGE3(), 5); + assert_eq!(bitfield.SSE4(), false); + } + #[test] + fn on_off() { + let mut bitfield = GeneratedBitField::from(23548); + assert_eq!(bitfield.RANGE1(), 0); + assert_eq!(bitfield.SSE(), true); + assert_eq!(bitfield.SSE1(), true); + assert_eq!(bitfield.RANGE2(), 3); + assert_eq!(bitfield.SSE2(), true); + assert_eq!(bitfield.SSE3(), false); + assert_eq!(bitfield.RANGE3(), 5); + assert_eq!(bitfield.SSE4(), false); + + bitfield.SSE_mut().off(); + bitfield.SSE1_mut().on(); + bitfield.SSE2_mut().on(); + bitfield.SSE4_mut().on(); + + assert_eq!(bitfield.RANGE1(), 0); + assert_eq!(bitfield.SSE(), false); + assert_eq!(bitfield.SSE1(), true); + assert_eq!(bitfield.RANGE2(), 3); + assert_eq!(bitfield.SSE2(), true); + assert_eq!(bitfield.SSE3(), false); + assert_eq!(bitfield.RANGE3(), 5); + assert_eq!(bitfield.SSE4(), true); + } + #[test] + fn checked_add_assign() { + let mut bitfield = GeneratedBitField::from(23548); + assert_eq!(bitfield.RANGE1(), 0); + assert_eq!(bitfield.SSE(), true); + assert_eq!(bitfield.SSE1(), true); + assert_eq!(bitfield.RANGE2(), 3); + assert_eq!(bitfield.SSE2(), true); + assert_eq!(bitfield.SSE3(), false); + assert_eq!(bitfield.RANGE3(), 5); + assert_eq!(bitfield.SSE4(), false); + + assert_eq!(bitfield.RANGE1_mut().checked_add_assign(1), Ok(())); + assert_eq!(bitfield.RANGE1(), 1); + + assert_eq!( + bitfield.RANGE1_mut().checked_add_assign(1), + Err(CheckedAddAssignError::Overflow) + ); + assert_eq!(bitfield.RANGE1(), 1); + + assert_eq!( + bitfield.RANGE2_mut().checked_add_assign(1), + Err(CheckedAddAssignError::Overflow) + ); + assert_eq!(bitfield.RANGE1(), 1); + + assert_eq!(bitfield.RANGE3_mut().checked_add_assign(2), Ok(())); + assert_eq!(bitfield.RANGE3(), 7); + + assert_eq!( + bitfield.RANGE3_mut().checked_add_assign(1), + Err(CheckedAddAssignError::Overflow) + ); + assert_eq!(bitfield.RANGE3(), 7); + + assert_eq!( + bitfield.RANGE3_mut().checked_add_assign(8), + Err(CheckedAddAssignError::OutOfRange) + ); + assert_eq!(bitfield.RANGE3(), 7); + + // We check all values are as expected at tend (we do this to ensure no operation overflow) + assert_eq!(bitfield.RANGE1(), 1); + assert_eq!(bitfield.SSE(), true); + assert_eq!(bitfield.SSE1(), true); + assert_eq!(bitfield.RANGE2(), 3); + assert_eq!(bitfield.SSE2(), true); + assert_eq!(bitfield.SSE3(), false); + assert_eq!(bitfield.RANGE3(), 7); + assert_eq!(bitfield.SSE4(), false); + } + #[test] + fn checked_sub_assign() { + let mut bitfield = GeneratedBitField::from(23548); + assert_eq!(bitfield.RANGE1(), 0); + assert_eq!(bitfield.SSE(), true); + assert_eq!(bitfield.SSE1(), true); + assert_eq!(bitfield.RANGE2(), 3); + assert_eq!(bitfield.SSE2(), true); + assert_eq!(bitfield.SSE3(), false); + assert_eq!(bitfield.RANGE3(), 5); + assert_eq!(bitfield.SSE4(), false); + + assert_eq!( + bitfield.RANGE1_mut().checked_sub_assign(1), + Err(CheckedSubAssignError::Underflow) + ); + assert_eq!(bitfield.RANGE1(), 0); + assert_eq!( + bitfield.RANGE1_mut().checked_sub_assign(2), + Err(CheckedSubAssignError::OutOfRange) + ); + assert_eq!(bitfield.RANGE1(), 0); + + assert_eq!(bitfield.RANGE2_mut().checked_sub_assign(1), Ok(())); + assert_eq!(bitfield.RANGE2(), 2); + + // We check all values are as expected at tend (we do this to ensure no operation overflow) + assert_eq!(bitfield.RANGE1(), 0); + assert_eq!(bitfield.SSE(), true); + assert_eq!(bitfield.SSE1(), true); + assert_eq!(bitfield.RANGE2(), 2); + assert_eq!(bitfield.SSE2(), true); + assert_eq!(bitfield.SSE3(), false); + assert_eq!(bitfield.RANGE3(), 5); + assert_eq!(bitfield.SSE4(), false); + } + #[test] + fn add_sub() { + let mut bitfield = GeneratedBitField::from(23548); + + let (mut range1, mut range2, mut range3) = (0, 3, 5); + check(&bitfield, range1, range2, range3); + + let mut rng = rand::thread_rng(); + // Randomly add and sub to `RANGE1` + for _ in 0..ITERATIONS { + let next = rng.gen_range(0..MAX); + if rng.gen() { + dbg!(next); + range1 += bitfield + .RANGE1_mut() + .checked_add_assign(next) + .map_or(0, |_| next); + } else { + dbg!(next); + range1 -= bitfield + .RANGE1_mut() + .checked_sub_assign(next) + .map_or(0, |_| next); + } + check(&bitfield, range1, range2, range3); + } + // Randomly add and sub to `RANGE2` + for _ in 0..ITERATIONS { + let next = rng.gen_range(0..MAX); + if rng.gen() { + dbg!(next); + range2 += bitfield + .RANGE2_mut() + .checked_add_assign(next) + .map_or(0, |_| next); + } else { + dbg!(next); + range2 -= bitfield + .RANGE2_mut() + .checked_sub_assign(next) + .map_or(0, |_| next); + } + check(&bitfield, range1, range2, range3); + } + // Randomly add and sub to `RANGE3` + for _ in 0..ITERATIONS { + let next = rng.gen_range(0..MAX); + if rng.gen() { + dbg!(next); + range3 += bitfield + .RANGE3_mut() + .checked_add_assign(next) + .map_or(0, |_| next); + } else { + dbg!(next); + range3 -= bitfield + .RANGE3_mut() + .checked_sub_assign(next) + .map_or(0, |_| next); + } + check(&bitfield, range1, range2, range3); + } + } + #[test] + fn checked_assign() { + let mut bitfield = GeneratedBitField::from(23548); + + let (mut range1, mut range2, mut range3) = (0, 3, 5); + check(&bitfield, range1, range2, range3); + + let mut rng = rand::thread_rng(); + // Randomly assign to `range1` + for _ in 0..ITERATIONS { + let next = rng.gen_range(0..MAX); + dbg!(next); + range1 = match bitfield.RANGE1_mut().checked_assign(next) { + Ok(()) => next, + Err(CheckedAssignError) => range1, + }; + check(&bitfield, range1, range2, range3); + } + // Randomly assign to `RANGE2` + for _ in 0..ITERATIONS { + let next = rng.gen_range(0..MAX); + dbg!(next); + range2 = match bitfield.RANGE2_mut().checked_assign(next) { + Ok(()) => next, + Err(CheckedAssignError) => range2, + }; + check(&bitfield, range1, range2, range3); + } + // Randomly assign to `RANGE3` + for _ in 0..ITERATIONS { + let next = rng.gen_range(0..MAX); + dbg!(next); + range3 = match bitfield.RANGE3_mut().checked_assign(next) { + Ok(()) => next, + Err(CheckedAssignError) => range3, + }; + check(&bitfield, range1, range2, range3); + } + } + #[test] + fn conversion() { + use std::collections::{HashMap, HashSet}; + + let mut rng = rand::thread_rng(); + + let bitfield_u8_before = BitFieldu8::from(rng.gen::()); + let (set, map) = + <(HashSet<&'static str>, HashMap<&'static str, u8>)>::from(&bitfield_u8_before); + let bitfield_u8_after = BitFieldu8::try_from((set, map)).unwrap(); + assert_eq!(bitfield_u8_before, bitfield_u8_after); + + let bitfield_u16_before = BitFieldu16::from(rng.gen::()); + let (set, map) = + <(HashSet<&'static str>, HashMap<&'static str, u16>)>::from(&bitfield_u16_before); + let bitfield_u16_after = BitFieldu16::try_from((set, map)).unwrap(); + assert_eq!(bitfield_u16_before, bitfield_u16_after); + + let bitfield_u32_before = BitFieldu32::from(rng.gen::()); + let (set, map) = + <(HashSet<&'static str>, HashMap<&'static str, u32>)>::from(&bitfield_u32_before); + let bitfield_u32_after = BitFieldu32::try_from((set, map)).unwrap(); + assert_eq!(bitfield_u32_before, bitfield_u32_after); + + let bitfield_u64_before = BitFieldu64::from(rng.gen::()); + let (set, map) = + <(HashSet<&'static str>, HashMap<&'static str, u64>)>::from(&bitfield_u64_before); + let bitfield_u64_after = BitFieldu64::try_from((set, map)).unwrap(); + assert_eq!(bitfield_u64_before, bitfield_u64_after); + + let bitfield_u128_before = BitFieldu128::from(rng.gen::()); + let (set, map) = + <(HashSet<&'static str>, HashMap<&'static str, u128>)>::from(&bitfield_u128_before); + let bitfield_u128_after = BitFieldu128::try_from((set, map)).unwrap(); + assert_eq!(bitfield_u128_before, bitfield_u128_after); + } + #[test] + fn serialize() { + let mut rng = rand::thread_rng(); + + let bitfield_u8_before = BitFieldu8::from(rng.gen::()); + let serialized = serde_json::to_vec(&bitfield_u8_before).unwrap(); + let bitfield_u8_after = serde_json::from_slice::(&serialized).unwrap(); + assert_eq!(bitfield_u8_before, bitfield_u8_after); + + let bitfield_u16_before = BitFieldu16::from(rng.gen::()); + let serialized = serde_json::to_vec(&bitfield_u16_before).unwrap(); + let bitfield_u16_after = serde_json::from_slice::(&serialized).unwrap(); + assert_eq!(bitfield_u16_before, bitfield_u16_after); + + let bitfield_u32_before = BitFieldu32::from(rng.gen::()); + let serialized = serde_json::to_vec(&bitfield_u32_before).unwrap(); + let bitfield_u32_after = serde_json::from_slice::(&serialized).unwrap(); + assert_eq!(bitfield_u32_before, bitfield_u32_after); + + let bitfield_u64_before = BitFieldu64::from(rng.gen::()); + let serialized = serde_json::to_vec(&bitfield_u64_before).unwrap(); + let bitfield_u64_after = serde_json::from_slice::(&serialized).unwrap(); + assert_eq!(bitfield_u64_before, bitfield_u64_after); + + let bitfield_u128_before = BitFieldu128::from(rng.gen::()); + let serialized = serde_json::to_vec(&bitfield_u128_before).unwrap(); + let bitfield_u128_after = serde_json::from_slice::(&serialized).unwrap(); + assert_eq!(bitfield_u128_before, bitfield_u128_after); + } +} diff --git a/tests/framework/dependencies.txt b/tests/framework/dependencies.txt index 3cea48997e8..b644f6efb77 100644 --- a/tests/framework/dependencies.txt +++ b/tests/framework/dependencies.txt @@ -9,12 +9,15 @@ 'base64 v0.13.1', 'bincode v1.3.3', 'bindgen v0.60.1', + 'bit-fields v0.1.0 (/firecracker/src/bit-fields)', + 'bit-fields-macros v0.1.0 (proc-macro) (/firecracker/src/bit-fields-macros)', 'bitflags v1.3.2', 'cc v1.0.77', 'cexpr v0.6.0', 'cfg-if v1.0.0', 'cipher v0.4.3', 'clang-sys v1.4.0', + 'convert_case v0.6.0', 'cpufeatures v0.2.5', 'cpuid v0.1.0 (/firecracker/src/cpuid)', 'crc64 v1.0.0', @@ -24,7 +27,7 @@ 'devices v0.1.0 (/firecracker/src/devices)', 'dumbo v0.1.0 (/firecracker/src/dumbo)', 'event-manager v0.3.0', - 'firecracker v1.1.0 (/firecracker/src/firecracker)', + 'firecracker v1.2.0 (/firecracker/src/firecracker)', 'generic-array v0.14.6', 'getrandom v0.2.8', 'ghash v0.5.0', @@ -33,7 +36,7 @@ 'io-lifetimes v0.6.1', 'io_uring v0.1.0 (/firecracker/src/io_uring)', 'itoa v1.0.4', - 'jailer v1.1.0 (/firecracker/src/jailer)', + 'jailer v1.2.0 (/firecracker/src/jailer)', 'kvm-bindings v0.6.0 ' '(https://github.com/firecracker-microvm/kvm-bindings?tag=v0.6.0-1#e8359204)', 'kvm-ioctls v0.12.0', @@ -57,29 +60,30 @@ 'opaque-debug v0.3.0', 'peeking_take_while v0.1.2', 'polyval v0.6.0', - 'proc-macro2 v1.0.47', + 'proc-macro2 v1.0.43', 'quote v1.0.21', 'rand_core v0.6.4', 'rate_limiter v0.1.0 (/firecracker/src/rate_limiter)', - 'rebase-snap v1.1.0 (/firecracker/src/rebase-snap)', + 'rebase-snap v1.2.0 (/firecracker/src/rebase-snap)', 'regex v1.7.0', 'regex-syntax v0.6.28', 'rustc-hash v1.1.0', 'rustix v0.34.8', 'ryu v1.0.11', - 'seccompiler v1.1.0 (/firecracker/src/seccompiler)', - 'serde v1.0.147', - 'serde_derive v1.0.147 (proc-macro)', - 'serde_json v1.0.89', + 'seccompiler v1.2.0 (/firecracker/src/seccompiler)', + 'serde v1.0.144', + 'serde_derive v1.0.144 (proc-macro)', + 'serde_json v1.0.85', 'shlex v1.1.0', 'snapshot v0.1.0 (/firecracker/src/snapshot)', 'subtle v2.4.1', - 'syn v1.0.103', + 'syn v1.0.99', 'thiserror v1.0.37', 'thiserror-impl v1.0.37 (proc-macro)', 'timerfd v1.3.0', 'typenum v1.15.0', - 'unicode-ident v1.0.5', + 'unicode-ident v1.0.3', + 'unicode-segmentation v1.10.0', 'universal-hash v0.5.0', 'userfaultfd v0.5.0', 'userfaultfd-sys v0.4.2', diff --git a/tests/integration_tests/build/test_coverage.py b/tests/integration_tests/build/test_coverage.py index 0e2be021c1e..03c87975d1c 100644 --- a/tests/integration_tests/build/test_coverage.py +++ b/tests/integration_tests/build/test_coverage.py @@ -47,7 +47,7 @@ GRCOV_VERSION = "0.7.1" -@pytest.mark.timeout(400) +@pytest.mark.timeout(600) def test_coverage(): """Test code coverage