bilge-impl 0.5.0

Use bitsized types as if they were a feature of rust.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
# bilge: the most readable bitfields

[![crates.io](https://img.shields.io/crates/v/bilge.svg)](https://crates.io/crates/bilge)
[![docs.rs](https://docs.rs/bilge/badge.svg)](https://docs.rs/bilge)
![loc](https://img.shields.io/badge/Lines%20of%20Code-4325-blue)

> [!NOTE]
>
> Special Thanks to all contributors and especially long-term maintainers [widberg]https://github.com/widberg and [pickx]https://github.com/pickx.


_Y e s_, this is yet another bitfield crate, but hear me out:

This is a _**bit**_ better than what we had before.

I wanted a design fitting rust:

- **safe**
    - types model as much of the functionality as possible and don't allow false usage
- **fast**
    - like handwritten bit fiddling code
- **simple to complex**
    - obvious and readable basic frontend, like normal structs
    - only minimally and gradually introduce advanced concepts
    - provide extension mechanisms (that means being modular! you can add more `derive`s easily)

The lib is **no-std** (and will be `const` again when rust [const-trait-impl](https://github.com/rust-lang/rust/issues/110395) works again).

For some more explanations on the "why" and "how": [blog post](https://hecatia-elegua.github.io/blog/no-more-bit-fiddling/) and [reddit comments](https://www.reddit.com/r/rust/comments/13ic0mf/no_more_bit_fiddling_and_introducing_bilge/).

## Usage

To make your life easier:

```rust
use bilge::prelude::*;
```

### Infallible (From)

You can just specify bitsized fields like normal fields:

```rust
#[bitsize(14)]
struct Register {
    header: u4,
    body: u7,
    footer: Footer,
}
```

The attribute `bitsize` generates the bitfield, while `14` works as a failsafe, emitting a compile error if your struct definition doesn't declare 14 bits.
Let's define the nested struct `Footer` as well:

```rust
#[bitsize(3)]
#[derive(FromBits)]
struct Footer {
    is_last: bool,
    code: Code,
}
```

As you can see, we added `#[derive(FromBits)]`, which is needed for `Register`'s getters and setters.
Due to how rust macros work (outside-in), it needs to be below `#[bitsize]`.
Also, `bool` will be saved as one bit.

`Code` is another nesting, this time an enum:

```rust
#[bitsize(2)]
#[derive(FromBits)]
enum Code { Success, Error, IoError, GoodExample }
```

Now we can construct `Register`:

```rust
let reg1 = Register::new(
    u4::new(0b1010),
    u7::new(0b010_1010),
    Footer::new(true, Code::GoodExample)
);
```

Or, if we want named arguments, we add `#[derive(BuilderBits)]` to `Register` and do:

```rust
let reg_built = Register::builder()
    .header(u4::new(0b1010))
    .body(u7::new(0b010_1010))
    .footer(Footer::new(true, Code::GoodExample))
    .build();
```

Or, if we add `#[derive(FromBits)]` to `Register` and want to parse a raw register value:

```rust
let mut reg2 = Register::from(u14::new(0b11_1_0101010_1010));
```

And getting and setting fields is done like this:

```rust
let header = reg2.header();
reg2.set_footer(Footer::new(false, Code::Success));
```

`bool` fields also get a toggle method:

```rust
let mut last = reg2.footer();
last.toggle_is_last();
reg2.set_footer(last);
```

Any kinds of tuple and array are also supported:

```rust
#[bitsize(32)]
#[derive(FromBits)]
struct InterruptSetEnables([bool; 32]);
```

Which produces the usual getter and setter, but also element accessors:

```rust
let mut ise = InterruptSetEnables::from(0b0000_0000_0000_0000_0000_0000_0001_0000);
// val_0 is the array, val_0_at is indexing into the array
let ise5 = ise.val_0_at(4);
ise.set_val_0_at(2, ise5);
assert_eq!(0b0000_0000_0000_0000_0000_0000_0001_0100, ise.value);
```

Depending on what you're working with, only a subset of enum values might be clear, or some values might be reserved.
In that case, you can use a fallback variant, defined like this:

```rust
#[bitsize(32)]
#[derive(FromBits, Debug, PartialEq)]
enum Subclass {
    Mouse,
    Keyboard,
    Speakers,
    #[fallback]
    Reserved,
}
```

which will convert any undeclared bits to `Reserved`:

```rust
assert_eq!(Subclass::Speakers, Subclass::from(2));
assert_eq!(Subclass::Reserved, Subclass::from(3));
assert_eq!(Subclass::Reserved, Subclass::from(42));
let num = u32::from(Subclass::from(42));
assert_eq!(3, num);
assert_ne!(42, num);
```

or, if you need to keep the exact number saved, use:

```rust
#[fallback]
Reserved(u32),
```

```rust
assert_eq!(Subclass2::Reserved(3), Subclass2::from(3));
assert_eq!(Subclass2::Reserved(42), Subclass2::from(42));
let num = u32::from(Subclass2::from(42));
assert_eq!(42, num);
assert_ne!(3, num);
```

### Fallible (TryFrom)

In contrast to structs, enums don't have to declare all of their bits:

```rust
#[bitsize(2)]
#[derive(TryFromBits)]
enum Class {
    Mobile, Semimobile, /* 0x2 undefined */ Stationary = 0x3
}
```

meaning this will work:

```rust
let class = Class::try_from(u2::new(2));
assert!(class.is_err());
```

except we first need to `#[derive(Debug, PartialEq)]` on `Class`, since `assert_eq!` needs those.

Let's do that, and use `Class` as a field:

```rust
#[bitsize(8)]
#[derive(TryFromBits)]
struct Device {
    reserved: u2,
    class: Class,
    reserved: u4,
}
```

This shows `TryFrom` being propagated upward. There's also another small help: `reserved` fields (which are often used in registers) can all have the same name.

Again, let's try to print this:

```rust
println!("{:?}", Device::try_from(0b0000_11_00));
println!("{:?}", Device::new(Class::Mobile));
```

And again, `Device` doesn't implement `Debug`:

### DebugBits

For structs, you need to add `#[derive(DebugBits)]` to get an output like this:

```rust
Ok(Device { reserved_i: 0, class: Stationary, reserved_ii: 0 })
Device { reserved_i: 0, class: Mobile, reserved_ii: 0 }
```

Also useful for debugging can be `#[derive(BinaryBits)]` (which adds underscores between fields):

```rust
// 0_0000_0_1_1_00000010000000010000_0_0_00
println!("{:032b}", redist.control.read());
```

For testing + overview, the full readme example code is in `/examples/readme.rs`.

### Other available derives:

- `DefaultBits`
- `SerializeBits`, `DeserializeBits`
- `JsonSchemaBits`

### Custom -Bits derives

One of the main advantages of our approach is that we can keep `#[bitsize]` pretty slim, offloading all the other features to derive macros.
Besides the derive macros shown above, you can extend `bilge` with your own derive crates working on bitfields.
An example of this is given in [`/tests/custom_derive.rs`](https://github.com/hecatia-elegua/bilge/blob/main/tests/custom_derive.rs), with its implementation in [`tests/custom_bits`](https://github.com/hecatia-elegua/bilge/blob/1dfb6cf7d278d102d3f96ac31a9374e2b27fafc7/tests/custom_bits/custom_bits_derive/src/lib.rs).

## Back- and Forwards Compatibility

The syntax is kept very similar to usual rust structs for a simple reason:

The endgoal of this library is to support the adoption of LLVM's arbitrary bitwidth integers into rust,
thereby allowing rust-native bitfields.
Until then, bilge is using the wonderful [`arbitrary-int` crate by danlehmann](https://github.com/danlehmann/arbitrary-int).

After all attribute expansions, our generated bitfield contains a single field, somewhat like:

```rust
struct Register { value: u14 }
```

This means you _could_ modify the inner value directly, but it can break type safety invariants (e.g. unfilled or read-only fields). Prefer setting `hide_value` and using the type-safe conversions `u14::from(register)` and `Register::from(u14)` when you need to work with the whole value.

On structs, `#[bitsize]` accepts:
- `hide_value`: make `value` completely inaccessible. The item is generated one module deeper, so relative visibility (`pub(super)`, `pub(self)`, inherited) is shifted one `super` up so the visibility you specify works like usual.
- `new = pub` or `new = pub(crate)`: the generated constructor is private by default but you can overwrite it like that

```rust
#[bitsize(14, hide_value, new = pub)]
#[derive(FromBits)]
struct Register {
    header: u4,
    body: u7,
    footer: Footer,
}

let mut reg = Register::new(
    u4::new(0b1010),
    u7::new(0b010_1010),
    Footer::new(true, Code::GoodExample)
);
let raw = u14::from(reg);
let _reg = Register::from(raw);
// reg.value = raw; // does not compile
```

### Placing fields with `#[at]`

Fields fill from bit 0 (LSB) in source order. `#[at(n)]` starts a field at bit `n` instead;
`#[at(n..=m)]` does the same and checks that the range width matches the field type.
Skipped bits are implicit padding. Overlap or going backwards is a compile error.

```rust
#[bitsize(16)]
struct Status {
    #[at(3)]
    ready: bool,   // bit 3
    #[at(8..=11)]
    nack: u4,      // bits 8..=11
}
```

The next field without `#[at]` continues after the previous one.
`#[bitsize(N)]` is still the total width: named fields may use fewer than `N` bits when `#[at]` is used.

### Tagged enums

`#[discriminant_at]` puts the tag in the same integer. `#[discriminant]` uses a separate tag value.

```rust
#[bitsize(32)]
#[discriminant_at(0..=6)]
enum Instr {
    R(RTypeRest) = 0b0110011,
    I(ITypeRest) = 0b0010011,
}

#[bitsize(8)]
#[discriminant(CrtcIndex)]
#[derive(TryFromBits)]
enum CrtcReg {
    Horiz(HorizontalDisplayEnd) = 0x01,
    MaxScan(MaxScanLine) = 0x09,
}

let reg = CrtcReg::try_from((index, data))?;
let (index, data) = reg.to_tag_and_data();
```

For some more examples and an overview of functionality, take a look at `/examples` and `/tests`.

## Alternatives

### benchmarks, performance, asm line count

[Comparison benches](https://github.com/hecatia-elegua/bilge/blob/main/compare/benches/compared/main.rs) (`cargo bench --manifest-path compare/Cargo.toml`) time construction from a host integer, field getters, a setter, and a combined MMIO-style round trip against modular-bitfield, bitbybit, bitfield-struct, deku, and a handwritten baseline. Integer-backed crates (bilge, bitbybit, bitfield-struct, handmade) are in the same ballpark; deku is a bit-stream parser and is much slower by design.

### build-time

Measured on rustc 1.96, using `cargo clean && cargo build --lib [--release] --quiet --timings`. **Total** is wall clock of that clean build (dependency graph included). **Crate** is rustc time of the bitfield package plus its proc-macro, read from the timings graph.

|                       | debug | debug crate | release | release crate |
|-----------------------|------:|------------:|--------:|--------------:|
| bilge 0.3             |   3.0 |         0.7 |     3.0 |           0.5 |
| bitbybit 2.0          |   2.7 |         0.5 |     2.8 |           0.5 |
| bitfield-struct 0.13  |   2.8 |         0.3 |     3.0 |           0.4 |
| modular-bitfield 0.13 |   2.9 |         0.7 |     2.9 |           0.7 |

Totals are dominated by `syn`, not by the bitfield crate itself. All four use syn 2 here. Macro expansion of one 8-bit struct is ~0.04s.

### handwritten implementation

The common handwritten implementation pattern for bitfields in rust looks [somewhat like compare/benches/compared/handmade.rs](https://github.com/hecatia-elegua/bilge/blob/main/compare/benches/compared/handmade.rs), sometimes also throwing around a lot of consts for field offsets. The problems with this approach are:
- readability suffers
- offset, cast or masking errors could go unnoticed
- bit fiddling, shifting and masking is done all over the place, in contrast to bitfields
- beginners suffer, although I would argue even seniors, since it's more like: "Why do we need to learn and debug bit fiddling if we can get most of it done by using structs?"
- reimplementing different kinds of _fallible nested-struct enum-tuple array field access_ might not be so fun

### modular-bitfield

The often used and very inspiring [`modular-bitfield`](https://github.com/robbepop/modular-bitfield) has a few
problems:
- (it is unmaintained and has a quirky structure) somebody took over maintainership
- constructors use the builder pattern
    - makes user code unreadable if you have many fields
    - can accidentally leave things uninitialized
- `from_bytes` can easily take invalid arguments, which turns verification inside-out:
    - modular-bitfield flow: `u16` -> `PackedData::from_bytes([u16])` -> `PackedData::status_or_err()?`
        - needs to check for `Err` on every single access
        - adds duplicate getters and setters with postfix `_or_err`
        - reinvents `From<u16>`/`TryFrom<u16>` as a kind of hybrid
    - bilge: usual type-system centric flow: `u16` -> `PackedData::try_from(u16)?` -> `PackedData::status()`
        - just works, needs to check nothing on access
        - some more general info on this: [Parse, don't validate]https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/
- big god-macro
    - powerful, but less readable to the devs of modular-bitfield
    - needs to cover many derives in itself, like `impl Debug` (other bitfield crates do this as well)
        - bilge: solves this by providing a kind of scope for `-Bits`-derives

and implementation differences:
- underlying type is a byte array
    - can be useful for bitfields larger than u128
        - bilge: if your bitfields get larger than u128, you can most often split them into multiple bitfields of a primitive size (like u64) and put those in a parent struct which is not a bitfield

Still, modular-bitfield is pretty good and I had set out to build something equal or hopefully better than it.
Tell me where I can do better, I will try.

### bitbybit

One of the libs inspired by the same crate is [`bitbybit`](https://github.com/danlehmann/bitfield), which is much more readable and up-to-date. Actually, I helped on that one in the past as well. After experimenting and hacking around in their code though, I realized it would need to be severely changed for the features and structure I had in mind.

implementation differences (as of 26.04.23):
- it can do read/write-only, array strides and repeat the same bits for multiple fields
    - bilge: these will be added the moment someone needs it and I or somebody else has time
- redundant bit-offset specification, which can help or annoy, the same way bilge's `reserved` fields can help or annoy
    - idea: [Optionally support specifying the bit-range of a field instead]https://github.com/hecatia-elegua/bilge/issues/28

### bitfield-struct

[`bitfield-struct`](https://github.com/wrenger/bitfield-struct-rs) (crates.io name `bitfield-struct`) is another integer-backed register crate, in the same runtime ballpark as bilge and bitbybit.

implementation differences:
- field widths are `#[bits(n)]` on primitive types (`u8`, `usize`, …), not `uN` aliases
- no fallible conversions (`TryFrom`); invalid bit patterns are mapped through `from_bits` instead
- has read/write-only fields (`access = RO`/`WO`), padding as `__`, optional `defmt`/`binrw`/`Hash`, and a custom endian `repr`

### deku

[`deku`](https://github.com/sharksforarms/deku) is a declarative binary parser, not an integer-backed register crate. I only found it via bitvec, and it produced ~20× more asm than other bitfield crates, probably because some stuff can't be `const` currently, like `DekuRead`.

### most others

Besides that, many bitfield libs try to imitate or look like C bitfields, even though these are hated by many.
I argue most beginners would have the idea to specify bits with basic primitives like u1, u2, ...
This also opens up some possibilities for calculation and conversion on those primitives.

Something similar can be said about `bitflags`, which, under this model, can be turned into simple structs with bools and enums.
[`bitbag`](https://docs.rs/bitbag) is the enum-as-flags version of that.
The main reason for these crates are that they allow overlapping presets. In `bilge`, these are currently made by adding extra methods (or using `TryFromBits` on an enum).

Basically, `bilge` tries to convert bit fiddling, shifting and masking into more widely known concepts like struct access.