Code Monkey home page Code Monkey logo

modular-bitfield's Introduction

Modular Bitfields for Rust

Continuous Integration Documentation Crates.io LoC
GHActions docs crates loc
  • no_std: Supports embedded development without std library.
  • This crate uses and generates 100% safe Rust code.

Description

Allows to have bitfield structs and enums as bitfield specifiers that work very similar to C and C++ bitfields.

Advantages

  • Safety: Macro embraced enums and structs are checked for valid structure during compilation time.
  • Speed: Generated code is as fast as handwritten code. (See benchmarks below.)
  • Modularity: Enums can be used modular within bitfield structs.

Attribution

Implements the #[bitfield] macros introduced and specified in David Tolnay's procedural macro workshop.

Thanks go to David Tolnay for designing the specification for the macros implemented in this crate.

Usage

Annotate a Rust struct with the #[bitfield] attribute in order to convert it into a bitfield. The B1, B2, ... B128 prelude types can be used as primitives to declare the number of bits per field.

#[bitfield]
pub struct PackedData {
    header: B4,
    body: B9,
    is_alive: B1,
    status: B2,
}

This produces a new constructor as well as a variety of getters and setters that allows to interact with the bitfield in a safe fashion:

Example: Constructors

let data = PackedData::new()
    .with_header(1)
    .with_body(2)
    .with_is_alive(0)
    .with_status(3);
assert_eq!(data.header(), 1);
assert_eq!(data.body(), 2);
assert_eq!(data.is_alive(), 0);
assert_eq!(data.status(), 3);

Example: Primitive Types

Any type that implements the Specifier trait can be used as a bitfield field. Besides the already mentioned B1, .. B128 also the bool, u8, u16, u32, u64 or u128 primitive types can be used from prelude.

We can use this knowledge to encode our is_alive as bool type instead of B1:

#[bitfield]
pub struct PackedData {
    header: B4,
    body: B9,
    is_alive: bool,
    status: B2,
}

let mut data = PackedData::new()
    .with_is_alive(true);
assert!(data.is_alive());
data.set_is_alive(false);
assert!(!data.is_alive());

Example: Enum Specifiers

It is possible to derive the Specifier trait for enum types very easily to make them also usable as a field within a bitfield type:

#[derive(BitfieldSpecifier)]
pub enum Status {
    Red, Green, Yellow, None,
}

#[bitfield]
pub struct PackedData {
    header: B4,
    body: B9,
    is_alive: bool,
    status: Status,
}

Example: Extra Safety Guard

In order to make sure that our Status enum still requires exatly 2 bit we can add #[bits = 2] to its field:

#[bitfield]
pub struct PackedData {
    header: B4,
    body: B9,
    is_alive: bool,
    #[bits = 2]
    status: Status,
}

Setting and getting our new status field is naturally as follows:

let mut data = PackedData::new()
    .with_status(Status::Green);
assert_eq!(data.status(), Status::Green);
data.set_status(Status::Red);
assert_eq!(data.status(), Status::Red);

Example: Recursive Bitfields

It is possible to use #[bitfield] structs as fields of #[bitfield] structs. This is generally useful if there are some common fields for multiple bitfields and is achieved by adding #[derive(BitfieldSpecifier)] to the attributes of the #[bitfield] annotated struct:

#[bitfield]
#[derive(BitfieldSpecifier)]
pub struct Header {
    is_compact: bool,
    is_secure: bool,
    pre_status: Status,
}

#[bitfield]
pub struct PackedData {
    header: Header,
    body: B9,
    is_alive: bool,
    status: Status,
}

With the bits: int parameter of the #[bitfield] macro on the Header struct and the #[bits: int] attribute of the #[derive(BitfieldSpecifier)] on the Status enum we can have additional compile-time guarantees about the bit widths of the resulting entities:

#[derive(BitfieldSpecifier)]
#[bits = 2]
pub enum Status {
    Red, Green, Yellow
}

#[bitfield(bits = 4)]
#[derive(BitfieldSpecifier)]
pub struct Header {
    is_compact: bool,
    is_secure: bool,
    #[bits = 2]
    pre_status: Status,
}

#[bitfield(bits = 16)]
pub struct PackedData {
    #[bits = 4]
    header: Header,
    body: B9,
    is_alive: bool,
    #[bits = 2]
    status: Status,
}

Example: Advanced Enum Specifiers

For our Status enum we actually just need 3 status variants: Green, Yellow and Red. We introduced the None status variants because Specifier enums by default are required to have a number of variants that is a power of two. We can ship around this by specifying #[bits = 2] on the top and get rid of our placeholder None variant while maintaining the invariant of it requiring 2 bits:

# use modular_bitfield::prelude::*;

#[derive(BitfieldSpecifier)]
#[bits = 2]
pub enum Status {
    Red, Green, Yellow,
}

However, having such enums now yields the possibility that a bitfield might contain invalid bit patterns for such fields. We can safely access those fields with protected getters. For the sake of demonstration we will use the generated from_bytes constructor with which we can easily construct bitfields that may contain invalid bit patterns:

let mut data = PackedData::from_bytes([0b0000_0000, 0b1100_0000]);
//           The 2 status field bits are invalid -----^^
//           as Red = 0x00, Green = 0x01 and Yellow = 0x10
assert_eq!(data.status_or_err(), Err(InvalidBitPattern { invalid_bytes: 0b11 }));
data.set_status(Status::Green);
assert_eq!(data.status_or_err(), Ok(Status::Green));

Benchmarks

Below are some benchmarks between the hand-written code and the macro-generated code for some example getters and setters that cover a decent variety of use cases.

We can conclude that the macro-generated code is as fast as hand-written code would be. Please file a PR if you see a way to improve either side.

  • cargo bench to run the benchmarks
  • cargo test --benches to run the benchmark tests

Click here to view all benchmark results.

Summary

The modular_bitfield crate generates bitfields that are ...

  • just as efficient as the handwritten alternatives.
  • equally efficient or more efficient than the alternative bitfield crate.

Showcase: Generated vs Handwritten

We tested the following #[bitfield] struct:

#[bitfield]
pub struct Generated {
    pub a: B9,  // Spans 2 bytes.
    pub b: B6,  // Within 2nd byte.
    pub c: B13, // Spans 3 bytes.
    pub d: B1,  // Within 4rd byte.
    pub e: B3,  // Within 4rd byte.
    pub f: B32, // Spans rest 4 bytes.
}

Note: All benchmarks timing results sum 10 runs each.

Getter Performance

get_a/generated     time:   [3.0990 ns 3.1119 ns 3.1263 ns]
get_a/handwritten   time:   [3.1072 ns 3.1189 ns 3.1318 ns]

get_b/generated     time:   [3.0859 ns 3.0993 ns 3.1140 ns]
get_b/handwritten   time:   [3.1062 ns 3.1154 ns 3.1244 ns]

get_c/generated     time:   [3.0892 ns 3.1140 ns 3.1491 ns]
get_c/handwritten   time:   [3.1031 ns 3.1144 ns 3.1266 ns]

get_d/generated     time:   [3.0937 ns 3.1055 ns 3.1182 ns]
get_d/handwritten   time:   [3.1109 ns 3.1258 ns 3.1422 ns]

get_e/generated     time:   [3.1009 ns 3.1139 ns 3.1293 ns]
get_e/handwritten   time:   [3.1217 ns 3.1366 ns 3.1534 ns]

get_f/generated     time:   [3.1064 ns 3.1164 ns 3.1269 ns]
get_f/handwritten   time:   [3.1067 ns 3.1221 ns 3.1404 ns]

Setter Performance

set_a/generated     time:   [15.784 ns 15.855 ns 15.932 ns]
set_a/handwritten   time:   [15.841 ns 15.907 ns 15.980 ns]

set_b/generated     time:   [20.496 ns 20.567 ns 20.643 ns]
set_b/handwritten   time:   [20.319 ns 20.384 ns 20.454 ns]

set_c/generated     time:   [19.155 ns 19.362 ns 19.592 ns]
set_c/handwritten   time:   [19.265 ns 19.383 ns 19.523 ns]

set_d/generated     time:   [12.325 ns 12.376 ns 12.429 ns]
set_d/handwritten   time:   [12.416 ns 12.472 ns 12.541 ns]

set_e/generated     time:   [20.460 ns 20.528 ns 20.601 ns]
set_e/handwritten   time:   [20.473 ns 20.534 ns 20.601 ns]

set_f/generated     time:   [6.1466 ns 6.1769 ns 6.2127 ns]
set_f/handwritten   time:   [6.1467 ns 6.1962 ns 6.2670 ns]

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this codebase by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

modular-bitfield's People

Contributors

crzysdrs avatar jam1garner avatar ldvsoft avatar lkolbly avatar qyriad avatar robbepop avatar worldsender avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

modular-bitfield's Issues

Deprecate new constructor for default impl

Currently the #[bitfield] proc. macro generates a new constructor that initializes all bits of the bitfield struct to 0.
Unfortunately this may invoke undefined behaviour for bit specifiers that do not allow for the zero bit pattern.
This is especially a problem with non-powers-of-two bit specifier enums.
This is not checked by the new constructor and what we want instead is a default constructor that initializes all the fields to their default values if available.
Besides that we might want to generate some builder-pattern-like or convoluted new constructor to avoid this problem.

Another idea is to make the new constructor only available via where bounds if all fields support the zero bit pattern when this is known at compile time. I think this solution (if it can be worked) would be particularly nice since the new constructor is really efficient and on top of that a const fn that would probably not yet be possible with any other safer constructor type.

Complex enums

I am writing a crate to define data structures for the USB specifications and the device class definitions that go along with them. USB contains requests, which are 8-byte structures stored in host memory and transmitted to the bus via a host controller interface (HCI) like xHCI. The fields for the requests are:

  • bmRequest, which is a bitmap of request attributes that specify where the data will be sent (host to device and vice-versa), the type of the request (standard USB request, class-specific, ...), and the recipient (a device, interface, ...);
  • The request code;
  • Request specific value and index parameters; and
  • The length of data transferred, if any.

The problem is that request codes overlap with each other depending on the type of request (e.g. if its a standard request, then its any of the requests defined in table 9-4 of the USB spec; if its class-specific then its a class-specific code; etc.). An example is a CLEAR_FEATURE request (a standard request with code 0x01) and SET_MIN (a USB audio class-specific request with the same code). Rust does not allow me to define two enumeration discriminants with the same value, but this crate requires me to not use discriminants with variants, so I can't simply define an enumeration variant per request type, and then have sub-variants for each class, for example, to overcome this limitation. Is there an alternative to solve this problem? I could define multiple request types (one per class for example) but that seems wasteful.

Setting field alters other field

Modifying almost any field changes the mode field back to the zero valued form of StatMode, HBlank.

src/main.rs

use modular_bitfield::prelude::*;
#[derive(BitfieldSpecifier, Debug, PartialEq, Copy, Clone)]
pub enum StatMode {
    HBlank = 0b00,
    VBlank = 0b01,
    OAM = 0b10,
    PixelTransfer = 0b11,
}

#[bitfield]
#[derive(Debug)]
pub struct StatFlag {  
    unused : B1,
    coincidence : bool,
    oam: bool,
    vblank : bool,        
    hblank : bool,
    coincidence_flag : bool,
    #[bits = 2]
    mode : StatMode,    
}

fn main() {
    let x = StatMode::VBlank;
    let mut flag = StatFlag::new();
    flag.set_mode(x);
    println!("{:?}", flag);
    assert_eq!(flag.get_mode(), x);
    flag.set_coincidence_flag(true); /* this alters the mode field */

    println!("{:?}", flag);
    assert_eq!(flag.get_mode(), x); // FAILS
}

Cargo.toml

[package]
name = "repr"
version = "0.1.0"
edition = "2018"

[dependencies]
modular-bitfield = "0.6.0"
> $ cargo run
>     Finished dev [unoptimized + debuginfo] target(s) in 0.00s
>      Running `target/debug/repr`
> StatFlag { data: [64] }
> StatFlag { data: [32] }
> thread 'main' panicked at 'assertion failed: `(left == right)`
>   left: `HBlank`,
>  right: `VBlank`', src/main.rs:32:5
> note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

Generate checked setters for #[bitfield]

The #[bitfield] macro currently only generates setters that panic upon out of bounds inputs.
Users might want checked setters that return a Result<(), OutOfBounds> (or similar) for better testability of the generated structs so we should implement it for them.

The open question is if we should generate this opt-in or make it possible to opt-out of it or if we just generate them alongside the normal setters.

Add documentation to all generated functions and respect visibility

Currently ::new() comes without documentation. But more importantly, it doesn't respect the annotated visibility of items.

The generated struct is declared public, the getters are public, etc. Instead it should use the visibility declared in on the fields and use that for the declared setters and getters.

As a workaround, I currently wrap all #[bitfield]s in a private module and expose them only through a wrapper.

Add #[repr = uN] annotation support for #[bitfield]

When given a #[bitfield] struct with a #[repr = uN], e.g. #[repr = u32] attribute the #[bitfield] macro shall make sure that the generated bitfield struct respects the bitwidth of the uN, e.g. 8 bits for u8, 16 bits for u16, etc.

Also it shall generate From<uN> and From<BitfieldStruct> for uN implementations.
Obviously having #[repr(uN)] also changes the underlying representation of the bitfield type to uN.

Example

#[bitfield]
#[repr(u32)]
struct TtResp {
    mregion: u8,
    sregion: u8, 
    mrvalid: bool,
    srvalid: bool,
    r: bool,
    rw: bool,
    nsr: bool,
    nsrw: bool,
    s: bool,
    irvalid: bool,
    iregion: u8,
}

This allows the user to only conditionally have the repr(u32) effects taken place using cfg_attr:

#[bitfield]
#[cfg_attr(test, repr(u32))]
struct TtResp {
    mregion: u8,
    sregion: u8, 
    mrvalid: bool,
    srvalid: bool,
    r: bool,
    rw: bool,
    nsr: bool,
    nsrw: bool,
    s: bool,
    irvalid: bool,
    iregion: u8,
}

Add #[skip] attribute for the fields of the #[bitfield] macro

Using the proposed #[skip] attribute on bitfield fields it is possible to entirely skip code generation for the flagged field. Also this allows to have two optional parameters, getters and setters that skips generation of the field's getters or setters respectively. By default #[skip] skips both, #[skip(getters)] only skips generation of getters, #[skip(setters)] only skips generation of setters and #[skip(getters, setters)] is just equal to #[skip].

Idea: Support wildcard as identifier for skipped fields in #[bitfield]

To skip code generation for a field f we already planned to implement the #[skip] attribute.
However, users still have to be creative to come up with some proper name for that field.
What would be better, was if fields named _ (wildcard) would simply be treated as skipped fields.

Example

use modular_bitfield::prelude::*;

#[bitfield]
pub struct Sparse {
    a: bool,
    _: B10, // We skip the next 10 bits.
    b: bool,
    _: B10, // We skip the next 10 bits again.
}

Problem

The problem with this proposed solution is that in Rust _ is not parsed as identifier but as a keyword and therefore causes problems beyond what is easily (without hacks) achievable in a proc. macro today.

Add out-of-bounds assertions for setters

Setting value through the generated set_* methods of #[bitfield] structs is currently unchecked.
So for some bit specifiers users could potentially insert invalid values with the unwanted effect of them being cut-off.

We should make set_* method panic upon out-of-bounds inputs.

Into<uN> and TryFrom<uN> for uN bitfields

hey thanks for making a neat crate!

it seems to me that it would be super useful to have core::convert::TryFrom<uN> and core::convert::Into<uN> implemented for generated bitfield types so one can use these types in a generic manner.

(as an aside, have y'all considered / would y'all be open to extending this with constants a-la bitflags? huge fan of not having to manage structure layouts, but i find myself missing the ability to use simple boolean operations on the objects.)

Implement serde derives replacement

Currently the easiest way to serialize/deserialize modular-bitfields using serde is by converting to/from another struct when serializing/deserializing. Ideally this could be handled automatically by modular-bitfield similarly to Debug impls (see #32).

I'd be willing to handle implementation, however I'd appreciate thoughts on if this is an acceptable feature for inclusion and if so any thoughts on how you like it to be implemented.

Clippy warns about "the operation is ineffective" and unable to set `#[allow(...)]`

Adding repr to the struct results in a clippy warning:

the operation is ineffective. Consider reducing it to `mregion: u8`

Adding #[allow(clippy::identity_op)] on top of the struct does not disable the warning.

Example (straight from the test):

use modular_bitfield::prelude::*;

#[bitfield]
#[repr(u32)]
#[derive(Debug, PartialEq, Eq)]
struct TtResp {
    mregion: u8,
    sregion: u8, 
    mrvalid: bool,
    srvalid: bool,
    r: bool,
    rw: bool,
    nsr: bool,
    nsrw: bool,
    s: bool,
    irvalid: bool,
    iregion: u8,
}

Rust: 1.52.0-nightly (d6eaea1c8 2021-03-14)
Clippy: 0.1.52 (d6eaea1c 2021-03-14)
Crate Version: 0.11.2

Curiosity about complex structures

So I was thinking of modifying a driver for NVMe hardware to use this crate (because I have to duplicate a lot of code in the driver as-is and that's just not practical, and I'm not sure how to do it in a generic). The layout of the structure has a couple things that I'm not sure how to repesent using this crate though:

  1. It contains strings (in particular, the serial number, model number and firmware revision), all longer than 128 bits. What is the best way of representing this? Are arrays possible? (I'd think so but want to check.)
  2. It contains bytes that are supposed to be read in big-endian. An example of this is the IEEE OUI identifier, which are bytes 75:73. Does this crate support mixed-endian structs?
  3. It contains bytes with reserved bits. For example, byte 76, Controller Multi-Path I/O and Namespace Sharing Capabilities (CMIC), is 8 bits, but bits 07:04 are reserved, and only bits 03:00 are used. Similarly, bytes 95:92, Optional Asynchronous Events Supported (OAES), contains reserved bits all over the place, e.g.: bits 31:15, 10, and 07:00. How should I best represent these kinds of bytes?
  4. In memory, the structure is packed, as though #[repr(C, packed)] was used. This is actually how I have to define them in my code at the moment because I can't read them in any other way unless I read them manually (oh god). Does the from_bytes()/to_bytes() structure count padding, and include that in consumption and generation of the byte stream, or does it only include the actual member fields?

For reference, the particular structures I'm talking about is over here, in figs. 249, 251, etc.: https://nvmexpress.org/wp-content/uploads/NVM-Express-1_4b-2020.09.21-Ratified.pdf

I would've written it here, but I didn't think that would be a good idea, seeing as its 4,096 bytes and I could just link to the information.

Support tuple structs

For example struct Example(B1, B7, B8) might have:

  • get_0 and set_0 for B1
  • get_1 and set_1 for B7
  • get_2 and set_2 for B8

Bitfield Specifier enum cannot have a variant called "In"

// src/lib.rs
use modular_bitfield::prelude::*;

#[derive(BitfieldSpecifier)]
enum Fails {
    Foo = 0,
    Bar = 1,
    In = 2,
    Out = 3
}
$ rustc --version
rustc 1.46.0 (04488afe3 2020-08-24)
$ cargo build
   Compiling example v0.1.0 (/tmp/example)
error: expected pattern, found keyword `in`
 --> src/lib.rs:3:10
  |
3 | #[derive(BitfieldSpecifier)]
  |          ^^^^^^^^^^^^^^^^^ expected pattern
  |
  = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)

error: proc-macro derive produced unparseable tokens
 --> src/lib.rs:3:10
  |
3 | #[derive(BitfieldSpecifier)]
  |          ^^^^^^^^^^^^^^^^^

error: aborting due to 2 previous errors

error: could not compile `example`.
$ rustc --version
rustc 1.48.0-nightly (d006f5734 2020-08-28)
$ RUSTFLAGS='-Z macro-backtrace' cargo build
   Compiling example v0.1.0 (/tmp/example)
error: expected pattern, found keyword `in`
   --> src/lib.rs:3:10
    |
3   |   #[derive(BitfieldSpecifier)]
    |            ^^^^^^^^^^^^^^^^^
    |            |
    |            expected pattern
    |            in this macro invocation
    |
   ::: /home/qyriad/.local/share/cargo/registry/src/github.com-1ecc6299db9ec823/modular-bitfield-impl-0.6.0/src/lib.rs:98:1
    |
98  | / pub fn bitfield_specifier(input: TokenStream) -> TokenStream {
99  | |     bitfield_specifier::generate(input.into()).into()
100 | | }
    | |_- in this expansion of `#[derive(BitfieldSpecifier)]`

error: proc-macro derive produced unparseable tokens
 --> src/lib.rs:3:10
  |
3 | #[derive(BitfieldSpecifier)]
  |          ^^^^^^^^^^^^^^^^^

error: aborting due to 2 previous errors

error: could not compile `example`.

This happens if it's typed as In or IN, but r#in and most curiously iN do work (but r#In and r#IN do not).

Remove the unnecessary braces around From implementations.

I'm currently using modular-bitfield for a project and am incurring in warnings generated by the the crate.

When a bitfield structure is annotated with a repr representation, the generated code will implement From-conversions to and from the represented type.

For example:

#[bitfield]
#[repr(u32)]
struct Foo {
    bar: B32,
}

Would produce the following code:

[...]
impl ::core::convert::From<::core::primitive::u32> for Foo
where
    [(); { 0usize + <B32 as ::modular_bitfield::Specifier>::BITS }]:
        ::modular_bitfield::private::IsU32Compatible,
{
    #[inline]
    fn from(__bf_prim: ::core::primitive::u32) -> Self {
        Self {
            bytes: <::core::primitive::u32>::to_le_bytes(__bf_prim),
        }
    }
}
impl ::core::convert::From<Foo> for ::core::primitive::u32
where
    [(); { 0usize + <B32 as ::modular_bitfield::Specifier>::BITS }]:
        ::modular_bitfield::private::IsU32Compatible,
{
    #[inline]
    fn from(__bf_bitfield: Foo) -> Self {
        <Self>::from_le_bytes(__bf_bitfield.bytes)
    }
}
[...]

In the where clause of the From implementations, the constant expression that gives the length of the of the array is enclosed in braces which are unneeded, producing the following compiler warning:

warning: unnecessary braces around const expression
[...]
N  | #[repr(u32)]
     | ^ help: remove these braces
[...]

This From expansion seems to happen in impl::bitfield::expand::expand_repr_from_impls_and_checks, where the following lines expand to the warned-about code:

fn expand_repr_from_impls_and_checks(&self, config: &Config) -> Option<TokenStream2> {
[...]
        let actual_bits = self.generate_target_or_actual_bitfield_size(config);
[...]
        quote_spanned!(span=>
            impl ::core::convert::From<#prim> for #ident
            where
                [(); #actual_bits]: ::modular_bitfield::private::#trait_check_ident,
[...]

Then, in generate_target_or_actual_bitfield_size, if the configuration has a None value for the bits field, the generation is delegated to generate_bitfield_size which generates the expression with the unneeded braces:

fn generate_bitfield_size(&self) -> TokenStream2 {
[...]
    quote_spanned!(span=>
        { #sum }
    )
}

I consider that the correct behavior here would be to avoid generating a warning by removing the braces when there is no need for them.

If it is desirable for the project to have this change, I would gladly make it myself as I need it for my project.

.with_field(value) syntax

Currently, we expose set_XYZ methods that return void, so if you want to populate a bitfield you have to have a mutable variable:

    let mut example = Example::new();
    example.set_a(true);
    example.set_b(0b0001_1111_1111_u16);
    example.set_c(42_u16);
    example.set_d(DeliveryMode::Startup);
    example.set_e(1);

What if there were a with_XYZ method which returned the bitfield object itself:

let example = Example::new()
    .with_a(true)
    .with_b(0b0001_1111_1111_u16)
    .with_c(42_u16)
    .with_d(DeliveryMode::Startup)
    .with_e(1);

?

I'm thinking a signature like:

fn set_a(self, value: bool) -> Self;

And, I guess, a with_a_checked which returns Result<Self>:

let example = Example::new()
    .with_a_checked(true)?
    .with_b_checked(...)?;

I think this would fit better in a closure-based modification API (similar to the API svd2rust provides):

my_register.modify(|value| { value.with_a(!value.a()) });

vs.

my_register.modify(|mut value| {
    value.set_a(!value.a());
    value
});

I can throw together a PR, but wanted to get thoughts first.

Silence "associated function is never used" warnings?

I've noticed that generated functions on a #[bitfield] struct will cause warnings when not used, such as this:

warning: associated function is never used: `new`
  --> src/scsi2sd_config.rs:34:1
   |
34 | #[derive(BinRead, Default)]
   | ^

In this case I have no need to create an empty bitfield (despite the Default trait), nor for into_bytes, but there doesn't seem to be a way to silence these warnings or disable these functions. If there's no way to prevent the warnings, perhaps making skip available at the top level (or adding a similar attribute) would be appropriate?

For reference, the struct I'm using is below, based off the binread bitfield example:

#[bitfield(bytes = 1)]
#[derive(BinRead, Default)]
#[br(map = Self::from_bytes)]
struct IdAndFlags {
    #[skip(setters)]
    scsi_id: B2,
    #[skip]
    __: B5,
    #[skip(setters)]
    is_enabled: bool
}

Anyhow, thanks for the nice crate! Its usage in my project is minor, but appreciated nonetheless :)

Add ::BITS or ::BYTES constant for generated structs

It would be helpful to have a constant on each bitfield struct that indicates its length, for example

#[bitfield]
pub struct MyFourBytes {
    a: B1,
    b: B3,
    c: B4,
    d: B24,
}

should have:

MyFourBytes::BITS; // equals 32

So I can easily determine the length of the struct, when reading/creating byte arrays.

Allow skipping into/from bytes and new functions

Hi,

I currently use bitfields to write bytes to and/or read bytes from a data bus. As some bitfields are only received while others are only sent, the bitfields cause dead_code warnings. Using #[allow(dead_code)] does not seem to have an effect.

As such, I would like it to be possible to skip the generation of these functions, much like it is possible to disable the byte boundry or to specify the size of the bitfield, like as such:

#[bitfield(skip_from_bytes)]
pub struct MeOnlySent {
}

#[bitfield(skip_into_bytes, skip_new)]
pub struct MeOnlyReceived {
}

#[bitfield(skip_new)]
pub struct MeOnlyForwardedAndModified {
}

If this option is already available somewhere or it is possible to avoid these warnings in some other manner, please give some direction. I could not find it.

Is it possible to make a bitfield for an array of derived BitfieldSpecifiers?

I'm working on my first rust project, so I hope I'm the one making mistakes.

My current code is as follows

mod data {
    use modular_bitfield::{
        BitfieldSpecifier,
        bitfield,
    };

    #[derive(BitfieldSpecifier)]
    pub enum Color {
        White,
        Black,
    }

    #[derive(BitfieldSpecifier)]
    #[bits=3]
    pub enum Rank { 
        Pawn,                // I'd rather have this as "Pawn(Color)" but that gives me a conversion error?
        Knight,
        Bishop,
        Rook,
        Queen,
        King,
        None,
    }

    #[bitfield]
    pub struct Board {
        state: [[(Rank, Color); 8]; 8] // Here's where the real problem lies
    }
}

As you can see, everything derives BitfieldSpecifier. But I'm getting the following error:

error[E0277]: the trait bound `[[(Rank, Color); 8]; 8]: Specifier` is not satisfied
  --> src/lib.rs:42:9
   |
42 |         state: [[(Rank, Color); 8]; 8]
   |         ^^^^^ the trait `Specifier` is not implemented for `[[(Rank, Color); 8]; 8]`
   |
   = note: required by `BITS`

How do I satisfy this Specifier trait for an array?

Add `bits = N` parameter to the #[bitfield] macro

With the proposed bits = N parameter for the #[bitfield] macro it will be possible to control how many bits the bitfield is going to use. For filled = true this is simply ensuring that exactly the amount of bits in the struct is going to be in use which must be divisible by 8. For filled = false this is, similar to #[derive(BitfieldSpecifier)] an indicator to the macro of how many bits there should be. This can be stretched to any value. For example, your bitfield actually just requires 7 bits in total but you want it to spread to 32-bits? No problem! Using filled = false, bits = 32 does the job for you.

Variable bitfield sizes in structs

I'm writing a dynamic language interpreter that uses pointer tagging to identify records in memory. It uses staged tagging, so that the number of tag bits can vary depending on the type of record. These records are primarily defined as values of:

#[bitfield]
#[repr(u64)]
#[derive(Copy, Clone, Debug)]
pub struct HeapCellValue {
    value: B56,
    f: bool,
    m: bool,
    tag: HeapCellValueTag,
}

where tag is a bitfield specifier enum:

#[derive(BitfieldSpecifier, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[bits = 6]
pub enum HeapCellValueTag {
    Cons = 0b00,
    F64 = 0b01,
    Atom = 0b1010,
    ...
}

I highlight Cons, F64 and Atom because their tag sizes are 2 bits, 2 bits, and 4 bits respectively. All other tags use the full 6 bits of HeapCellValueTag. For the smaller tags, the remaining tag bits of HeapCellValue are used for other data. In particular, Atom-tagged cells are actually formatted like this:

#[bitfield]
#[repr(u64)]
#[derive(Copy, Clone, Debug)]
pub struct AtomCell {
    name: B45,
    prec: B11,
    spec: B3,
    m: bool,
    tag: B4,
}

My problem is that I can't extract the smaller tags from HeapCellValue values using the generated bitfield functions without adjacent bits of AtomCell being included in the resulting HeapCellValue's tag field upon conversion. Is it possible to access the bits of the bitfield without making assumptions about the endianness of the machine?

Getting a reference to sub-struct

I'm not sure how feasible it is, but I would like to implement some functions that take &mut self on my sub-structs

Right now I can only set fields in the main struct via set_foo(), if I want to set a subfield I therefore have to do something like
self.set_foo(self.foo().with_bar(true))

instead I would like some way of getting a reference to an object i.e self.foo_ref_mut().set_bar(true)

this would also allow custom functions to be implemented on the substructs that take references rather than making copies.

Think about structs in structs

I don't have anything figured out for this but it would be worth thinking about: what would it take to support factoring shared groups of fields like a common header? This would be in line with the theme of modularity.

struct H {
    a: B2,
    b: B3,
}

struct M {
    h: H,
    c: B11,
}

struct N {
    h: H,
    c: B19,
}

Implement `AsRef` and `AsMut` instances for `repr(uN)` bitfields

For repr(uN) bitfields, it'd be helpful to be able to view a reference to the bitfield as a reference to the representation type without copy.

The opposite is also true - there are occasions when I'd like to operate on a primitive type as though it were a bitfield.

Make it possible to generate getters and setters as const fn

Currently the getters and setters generated by the #[bitfield] proc. macro for its bitfield struct are not const fn.
This is because they are making heavy use of functionality that current stable Rust does not yet support within const fn.
However, having those getters and setters as const fn is undoubtly to great value and should probably be possible with serious changes to the code generation and the backend.

This is probably a more involved task.

Provide a way to derive a sensible Debug impl

Currently, the behavior results in the following:

            QuickDir {
                bytes: [
                    111,
                    242,
                    94,
                    29,
                    5,
                    111,
                    0,
                    0,
                    44,
                    10,
                    0,
                    0,
                ],
            },

Since the attribute macro runs before the derive happens, it should be possible to check for #[derive(Debug)], remove Debug from the list of traits to be derived, and manually derive Debug ourselves. This would allow for Debug to look the way it would if you didn't use #[bitfield].

I'm willing to do an implementation, just want to make sure this fits in with the design goals before starting work on it.

Add `filled: bool` parameter to #[bitfield] proc. macro

The proposed filled: bool parameter for the #[bitfield] proc. macro allows to have bitfields with bit widths that are not divisible by 8 and therefore might contain invalid bit patterns that are inaccessible from the outside.
E.g. creating a #[bitfield(filled = false)] type with only 5 bits will leave 3 bits completely undefined.

For those #[bitfield] structs we no longer can define the byte conversion routines as infallible operations.
Instead they need to return Result and check whether the undefined bits are populated.

Not break the world

In this vein we will declare #[bitfield(specifier = true)] structs to be implicitly filled = false with the same effects.
Having #[bitfield(specifier = true, filled = true)] is only valid in case the bitfield struct has a bit width that is divisible by 8.

For a future release we might rethink this approach and instead no longer make #[bitfield(specifier = true)] implicitly be filled = false with all the implied consequences.

Add u128 support

Possibly behind a feature if it turns out to affect compile time.

Re-expand attributes on bitfields for expanded getters and setters

Currently when attaching non-bitfield specific attributes onto fields of a #[bitfield] annotated struct they are simply lost.
This is bad since users might want to have a direct influence onto some generated setters and getters.
An example use case might be to add some specific documentation or disable certain warnings but there might be more!

This issue proposes to re-expand non-bitfield specific attributes for a given field for all expanded getters and setters of it.
In the future we might want to provide the user with more control and take over control for which getters and setters the attribute shall be expanded but this issue is not concerned about this.

Automatically implement generic bitfield trait for #[bitfield] structs

Would it be possible to create a trait where the Bitfield methods are defined? Or maybe this already exists?

Thinking about the following:

  • from_bytes
  • as_bytes

I want to create a generic function that takes bitfields and writes them to a device, but currently I don't have anything to enforce type bounds on.

Allow returning bytes in MSB order

Currently (at least on my platform, ARMv7), bytes are returned in LSB order but my driver requires them to be MSB. How tricky would it be to add a configuration option for this?

Example:

#[bitfield]
pub struct MyBitfield {
  foo: B16,
}

let test = MyBitfield::new().with_foo(0x1234u16);

test.as_bytes() returns [0x34, 0x12] but I need it to be [0x12, 0x34]. While I could reverse the order of the result from as_bytes, it is error-prone and would IMO be better captured by the bitfield itself.

Add no_std support

Seems like it wouldn't need to be behind a feature -- I don't see any imports from std in the modular-bitfield crate.

Disjoint Fields

Hello and thank you for your excellent crate, I have been reading through it for a while before I start a project and I wanted to get your opinion about how to best tackle an issue in my project.

I am planning on writing some crates to support Garmin's ANT spec and most of their data is LE and contiguous fields... except one legacy profile which has the following layout.

Bit 0-7 8-11 12-15 16-23
Field LSB Field A LSN Field B MSN Field A MSB Field B

Does your library have any mechanism to resolve this legacy issue or do you know if there are any tricks I can employ to get around this?

P.S. I am new to rust, and am planning to use this project to get better with the language. Also I am avoiding building anything too big as the plan is to support embedded targets.

Add `bytes = N` parameter to the `#[bitfield]` macro

For checking whether a #[bitfield] struct is generated to be exactly N bytes big we could add a bytes = N parameter to the #[bitfield] proc. macro.
For example for N = 32 a bitfield struct with a bid width of 25 up to 32 bits is going to be valid.

Example

#[bitfield(bytes = 4)]
struct TtResp {
    mregion: u8,
    sregion: u8, 
    mrvalid: bool,
    srvalid: bool,
    r: bool,
    rw: bool,
    nsr: bool,
    nsrw: bool,
    s: bool,
    irvalid: bool,
    iregion: u8,
}

Design Question: Change syntax of #[bitfield(specifier = true)]

Currently to automatically derive the Specifier trait for a #[bitfield] annotated struct is done as follows:

#[bitfield(specifier = true)]
pub struct SignedInt {
    sign: bool,
    value: B31,
}

This is quite clunky and not ideal Rust syntax. A more Rusty approach would be the following syntax:

#[bitfield]
#[derive(BitfieldSpecifier)]
pub struct SignedInt {
    sign: bool,
    value: B31,
}

While this is syntactically closer to what a Rust user would expect it has the downside of the BitfieldSpecifier identifier not being hygienic. A user could introduce their own trait called BitfieldSpecifier with their own derive macro which would probably interfere with their expectations of the actually derived macro which in this case would still be the one coming from the modular_bitfield crate.

Provide bit layout diagrams

Details about endianness are not self-explanatory from the readme code sample. The code shows that we can put data in and take the same data out, but not how to think about the data representation in memory which would be important for using modular-bitfield to communicate with other systems.

Compilation broken for 0.10.0 for Rust edition 2015

This code does not compile in 0.10.0

#[bitfield(specifier = true)]
#[derive(Copy, Clone, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct OpMode {
  reserved: B2,
  pub modem_mode: ModemMode,
  pub listen_abort: bool,
  pub listen_on: bool,
  pub sequencer_off: bool,
}
error[E0433]: failed to resolve: use of undeclared type or module `dyn`
  --> src/register.rs:39:3
   |
39 |   reserved: B2,
   |   ^^^^^^^^ use of undeclared type or module `dyn`

Suspicion: https://stackoverflow.com/questions/51071925/error-e0433-when-dyn-used-with-absolute-path

.map(|__bf_field| __bf_field as &dyn ::core::fmt::Debug)

Design: Make bitfields with `__` (double wildcard) identifier skip by default

Currently if you want to skip a bit field and are not interested in coming up with a name for it the best technique you can employ is:

#[bitfield]
struct HalfSkipped {
    #[skip] __: B2,
    value: B4,
    #[skip] __: B2,
}

This issue proposes to automatically skip all fields with a name equal to __ (double wildcard) turning the above example into:

#[bitfield]
struct HalfSkipped {
    __: B2,
    value: B4,
    __: B2,
}

Why double wildcard instead of a single one?
- A standard Rust parser does not accept a single wildcard (_) as an identifier making it troublesome to use this in a proc. macro.


Unresolved: I am unsure about this design proposal since it is less transparent to a user than what we have right now. However, in case of many entirely skipped bit fields this is seriously cleaner syntactically.

Feature request: Array support

Currently, the following is not supported:

#[bitfield]
pub struct Testing {
    #[skip] __: [bool; 512],
}

It will complain with the error below:

error[E0277]: the trait bound `[bool; 512]: modular_bitfield::Specifier` is not satisfied
   --> src/test.rs:41:5
    |
41  |     #[skip] __: [bool; 512],
    |     ^^^^^^^^^^^^^^^^^^^^^^^ the trait `modular_bitfield::Specifier` is not implemented for `[bool; 512]`
    |
note: required by `modular_bitfield::Specifier::BITS`
   --> /Users/visual/.cargo/registry/src/github.com-1ecc6299db9ec823/modular-bitfield-0.11.2/src/lib.rs:455:5
    |
455 |     const BITS: usize;
    |     ^^^^^^^^^^^^^^^^^^

Design API for converting data to/from bytes

The current #[bitfield] API is self-contained in the sense that within a single Rust application we can place data into bitfields and get the data back out, which is potentially useful for optimizing memory usage of memory-intensive applications but not the primary use of bitfields. For interacting with other systems, we would need more than this. For example to use modular-bitfield to manipulate incoming data packets from the outside world -- this isn't a thing that can be implemented using new and get_* and set_*. Would transmute be the recommended way for downstream code to solve this or is there a better approach?

Atomics support

It would be cool if bitfield loads and stores could also be atomic. For example, it is very usable in GC systems where object color might be encoded in 2 or 3 bits, and during parallel/concurrent collection these bits should be updated atomically.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.