OpenCyphal / nunavut

Generate code from DSDL using PyDSDL and Jinja2
https://nunavut.readthedocs.io/
Other
37 stars 24 forks source link

Rust Type Generation Support #209

Closed davidlenfesty closed 1 year ago

davidlenfesty commented 3 years ago

(In support of the uavcan.rs v1 rewrite)

Rust's type system maps extremely well to UAVCAN's types (struct -> struct, union -> enum), so this should be relatively easy.

In conversation with Alexander Hübener, it was decided we should go with Rust macros to provide serialization, just to homogenize as much as possible to Rust. As well, Rust has excellent testing infrastructure that we can take advantage of, without having to find a way to integrate it into Nunavut's testing.

Definitely not finalized, but this is approximately how I think the generated code should look.

uavcan/node/Heartbeat.1.0.uavcan

// Some namespace collision here, fairly trivial to resolve, but will have to come up with a solution
use uavcan::{Serialize, Deserialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
// Values here are placeholder, no idea what they are, also didn't exhaustively cover attributes
#[uavcan(extent=56)]
pub struct Heartbeat_1_0 {
    #[uavcan(bit_width=32, alignment=1)]
    pub uptime: u32,
    pub health: crate::uavcan::node::Health_1_0,
    pub node: crate::uavcan::node::Mode_1_0,
    #[uavcan(bit_width=8, alignment=1)]
    pub vendor_specific_status_code: u8,
}

I'm not entirely sure how the folder structure will have to work out, specifically:

pavel-kirienko commented 3 years ago

@davidlenfesty do you think we still need the dsdl_parser.rs, or should its repository be archived?

Nunavut's C generator comes with a rather extensive yet simple test suite that I recommend looking at:

In v1, serialization rules are substantially more involved than in v0 due to its support for extensibility, structural polymorphism, and new alignment rules. I am stating this here to ensure the effort estimates are not overly optimistic. I am certainly at your disposal though if you have specific questions regarding the implementation. I think if you (hypothetically) were to let Nunavut generate serialization code, you would be able to cut quite a few corners by heavily relying on the existing C backend (assuming here that the auto-generated serialization logic is going to be unsafe due to heavy low-level memory manipulation, not that it's a requirement though).

There's also one vague idea that I'm not sure about: do you think integration with serde is feasible and sensible in this case, regardless of the chosen code generation approach?

davidlenfesty commented 3 years ago

I don't think we need dsdl_parser.rs, I definitely don't plan on supporting it, and it's only really required if we want to avoid nunavut entirely.

More discussion about where the nunavut/Rust split should happen, but Alexander expressed interest in writing the serialization code using proc macros specifically for his thesis work, and I do generally prefer the serialization logic living in Rust rather than Jinja2 templates. Either way, a minimum implementation that simply generates the type definitions is required and where I will start work.

That's a good reminder, I think we should derive serde's Serialization and Deserialization traits for all of the generated structs/enums, so that these messages can easily be exported to other forms, but I don't think that serde provides enough information for us to serialize DSDL directly. (I may be wrong, that's from a fuzzy recollection and it's worth looking into.)

thirtytwobits commented 3 years ago

At a high-level, Nunavut is where we should do any code generation. For example, pyuavcan generates types dynamically and so it does not need Nunavut. If the same is true for Rust then you can skip Nunavut, however, if we are going to generate Rust code I'd prefer having it in Nunavut even if it uses something other than Python or Jinja to generate this code. The reason being; it provides us a single place to cross-verify all serialization logic to drive consistency between our implementations and it insulates changes to type serialization internals from the implementations that use these types. Frankly, I'd rather Nunavut was implemented in Go or some other language that compiles to native code. The fact we've implemented it in Python should be orthogonal to the decision to use this project to generate code for any given target language.

davidlenfesty commented 2 years ago

Valid points, ultimately for the immediate future it's up to what Alexander wants to do/can do with his thesis for the serialization/deserialization side, as I don't have the bandwidth for much more than the basic type generation at the moment.

teamplayer3 commented 2 years ago

Yes, @thirtytwobits you are correct with your thoughts of having all the Type generation stuff at one place. The thoughts of having type generation in Rust were to have a quick prototyping of messages. This would be a more experimental implementation. By now, I think it's important to have it working with Nunavut.

In Rust, if it's done with macros, it's a compile-time thing. And would give no runtime overhead. So, it doesn't matter if the crate is used in embedded or on desktop pcs.

@davidlenfesty maybe I have to give up this topic. Or do it if I have some time over. The direction of the thesis changed a little.

pavel-kirienko commented 1 year ago

Closing as obsolete due to https://github.com/OpenCyphal-Garage/cyphal.rs/issues/102