On 5/5/25 14:26, Manos Pitsidianakis wrote:
    Something I do notice is that there's some inconsistency in
    how we've structured things between the two devices, e.g.:

    * the pl011 main source file is device.rs, but the hpet one
      is hpet.rs

    * some places we use the actual names of bits in registers
      (eg Interrupt's OE, BE, etc consts), and some places we
      seem to have renamed them (e.g. pl011 Flags has clear_to_send
      not CTS, etc)

    * pl011 has defined named fields for its registers, but hpet does
      things like::

         self.config.get() & (1 << HPET_CFG_LEG_RT_SHIFT) != 0

    * pl011 has a split between PL011State and PL011Registers,
      but HPET does not. As I mentioned in an email thread a
      little while back, I feel like the State/Registers split
      is something we should either make a more clear deliberate
      formalised separation that's part of how we recommend
      device models should be designed

    [...]

    I think it would be good to figure out what we think is the
    right, best style, for writing this kind of thing, and be
    consistent. We have long standing problems in the C device
    models where there are multiple different styles for how
    we write them, and it would be good to try to aim for
    more uniformity on the Rust side.

The pl011 stuff was deliberate decisions:

- device.rs vs pl011.rs: the device was written as a crate, so it's
essentially its own library, plus pl011/src/pl011.rs would be
redundant :)

Right, I think Peter's comment was more about moving hpet.rs to device.rs, and merging PL011's device_class.rs into its device.rs.

   That said, it's not important, we can choose either convention. I
like the less redundancy and separation of concerns: if pl011 gets
converted into a module in a future refactor, it could keep its
functionality split into different submodules and `pl011.rs` or
`pl011/mod.rs` would be the device module.

I think it's okay to decide that Rust devices will have mini directories: it's just the style of the language and Cargo more or less relies on having lib.rs.

In a vacuum I would prefer to have hw/char/pl011.rs for what is now rust/hw/char/pl011/lib.rs, and place the other files in hw/char/pl011; IIRC rustc accepts that style. However we still rely on Cargo for some things(*), and as long as we do there's not much we can do about it.

    (*) notably "cargo fmt".  Everything else is more or less handled
        by Meson starting with 1.8.0.

- Using typed registers instead of constants: yes coming from C I can
understand it can feel unfamiliar. I specifically wanted to make the
register fields typed to avoid making the implementation a "C port",
and I think it's worthwhile to use the type system as much as
possible.

Peter's comments (especially the second and third) were about two kinds of inconsistencies:

1) HPET not using bilge. This was because Zhao looked at HPET from the opposite direction compared to what you did on pl011, namely avoiding unsafe and modeling the BQL properly, and sacrificed a bit the usage of idiomatic code like what bilge provides.

I think that you made the right choice for the first device and he made the right choice for the second device. But now someone should look at HPET and do the work that you did to adopt bilge.

2) The choice between bilge on one side, and bitflags or integers on the other. For pl011 you kept interrupt bits as integers for example, and this is related to the topic of (non-)availability of const in traits...

   A straight C port would result into integer constants with integer
typed fields everywhere for registers/flags.
   Yes, From/Into aren't const, at least yet, but it's not really a
hotpath performance wise. I think non-dynamically dispatched trait
methods can be inlined by annotating the `fn from(..)` impl with a
`#[inline(always)]` hint but I haven't confirmed this, just
speculation.

It's not about hot paths, it's more that 1) you cannot use From/Into in a "static"'s initializer 2) bilge relies a lot on non-const methods in its internal implementation, which makes it quite messy to use it in some places. See for example this thing for which I take all the blame:

    impl Data {
        // bilge is not very const-friendly, unfortunately
        pub const BREAK: Self = Self { value: 1 << 10 };
    }

and the same would be true of interrupt constants and the IRQMASK array.

The separate bilge and bitflags worlds are what bothers me the most in the experimental devices. I can see why they would be very confusing for someone who's not had much experience with Rust, and therefore doesn't know *why* they are separate.

   Again, no strong opinions here. I like the "everything is typed"
approach and I think it's worth it to explore it because it allows us
to "make invalid/illegal states unrepresentable" as one sage article
goes.

I agree, and it's why I think you made the right choice using it for pl011. With all the unsafe code that you had to use, strong-typing at least showed *something* that Rust could provide compared to C(*). And I like the strong typing too, even if I'm not sure I like bilge's *implementation* that much anymore.

I'm not really up for rewriting it, but then I've also done more stupid rewrites in the past. :)

   (*) when we were discussing safety vs. unsafety last summer, I may
       have sounded dismissive of this kind of benefit.  My point at the
       time was that unsafe code was so much more complex than C, that
       the benefit of strong-typing wasn't enough to *offset* the
       complexity of unsafe code.  But it is absolutely present.

Related to this I have found recently the `attrs crate
<https://docs.rs/attrs/>`__, which provides an easy way to parse the
contents of attributes in a procedural macro.

I actually have some WIP patches for this I put a pause on and can
continue e.g. 
https://gitlab.com/epilys/rust-for-qemu/-/commit/c2c97caaaf03273fabc14aee5a4d1499668ddbe3

The repository is private, but I look forward to seeing it! If you want to post an RFC without even any code, just to show what the device code looks like, that would be helpful as it will catch stuff like lack of type safety.

BTW, if you need it to model reflection better I think it is acceptable to assume const_refs_to_static is present.

Paolo

Reply via email to