rust: add "bits", a custom bitflags implementationOne common thing that device emulation does is manipulate bitmasks, for exampleto check whether two bitmaps have common bits. One example in the
rust: add "bits", a custom bitflags implementationOne common thing that device emulation does is manipulate bitmasks, for exampleto check whether two bitmaps have common bits. One example in the pl011 crateis the checks for pending interrupts, where an interrupt cause corresponds toat least one interrupt source from a fixed set.Unfortunately, this is one case where Rust *can* provide some kind ofabstraction but it does so with a rather Perl-ish There Is More Way ToDo It. It is not something where a crate like "bilge" helps, becauseit only covers the packing of bits in a structure; operations like "areall bits of Y set in X" almost never make sense for bit-packed structs;you need something else, there are several crates that do it and of coursewe're going to roll our own.In particular I examined three:- bitmask (https://docs.rs/bitmask/0.5.0/bitmask/) does not support const at all. This is a showstopper because one of the ugly things in the current pl011 code is the ugliness of code that defines interrupt masks at compile time: pub const E: Self = Self(Self::OE.0 | Self::BE.0 | Self::PE.0 | Self::FE.0); or even worse: const IRQMASK: [u32; 6] = [ Interrupt::E.0 | Interrupt::MS.0 | Interrupt::RT.0 | Interrupt::TX.0 | Interrupt::RX.0, ... } You would have to use roughly the same code---"bitmask" only helps with defining the struct.- bitmask_enum (https://docs.rs/bitmask-enum/2.2.5/bitmask_enum/) does not have a good separation of "valid" and "invalid" bits, so for example "!x" will invert all 16 bits if you choose u16 as the representation -- even if you only defined 10 bits. This makes it easier to introduce subtle bugs in comparisons.- bitflags (https://docs.rs/bitflags/2.6.0/bitflags/) is generally the most used such crate and is the one that I took most inspiration from with respect to the syntax. It's a pretty sophisticated implementation, with a lot of bells and whistles such as an implementation of "Iter" that returns the bits one at a time.The main thing that all of them lack, however, is a way to simplify theugly definitions like the above. "bitflags" includes const methods thatperform AND/OR/XOR of masks (these are necessary because Rust operatoroverloading does not support const yet, and therefore overloaded operatorscannot be used in the definition of a "static" variable), but they becomeeven more verbose and unmanageable, like Interrupt::E.union(Interrupt::MS).union(Interrupt::RT).union(Interrupt::TX).union(Interrupt::RX)This was the main reason to create "bits", which allows something like bits!(Interrupt: E | MS | RT | TX | RX)and expands it 1) add "Interrupt::" in front of all identifiers 2) convertoperators to the wordy const functions like "union". It supports booleanoperators "&", "|", "^", "!" and parentheses, with a relatively simplerecursive descent parser that's implemented in qemu_api_macros.Since I don't remember exactly how the macro was developed, I cannot excludethat it contains code from "bitflags". Therefore, I am conservatively leavingin the MIT and Apache 2.0 licenses from bitflags. In fact, I think therewould be a benefit in being able to push code back to "bitflags" anywaywhenever applicable, so that the two libraries do not diverge too much,so that's another reason to use this.Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
show more ...