Message-plane interface-description codegen — architecture (arc42)¶
Architecture documentation for the message-plane interface-description codegen toolchain (see Message-plane interface-description codegen), structured per the arc42 template and encoded with sphinx-needs using the useblocks “x-as-code” arc42 directive types. Mirrors the structure of Device-driver codegen — architecture (arc42) and CANopen device-driver codegen — architecture (arc42) so reviewers can read the device-plane and message-plane umbrellas 1:1.
Each architectural element :refines: or :implements: a parent
requirement so the trace is preserved end-to-end.
This chapter is split across pages (see the toctree): the framing sections §1–§3 (goals, constraints, context and scope — including the crate-dependency-graph mermaid) live here on the index; the building blocks (§4) live in Building block view; the solution strategy and its ADRs (§5) live in Solution strategy and decisions; and the risks (§6) live in Risks and technical debt.
1. Introduction and goals¶
The toolchain’s reason-to-exist is build-time monomorphisation of
message (de)serializers: an interface description (a CAN .dbc today)
declares the messages on a channel; we want strongly-typed
encode/decode code per message, with a runtime path small enough
to audit and free of serde and heap allocation.
Quality goals capture the qualities the architecture is optimised for.
Every type the IR can express shall have a finite, compile-time-known
serialized-length bound, and that property shall be unrepresentable to
violate — |
The only crate that links into a runtime consumer
( |
Each crate shall depend only on crates to its left in the IR → wire → frontend → codegen → backend chain (see Toolchain layering (crate d... (ARCH_0081)). The IR knows no wire format; the codegen knows no wire format; only a backend does. Collapsing the layering — a frontend reaching for a backend, the IR naming a transport — is rejected; the layering is the design. |
2. Constraints¶
|
3. Context and scope¶
Five layers, strict left-to-right dependency. The IR and the wire runtime are the two roots; a frontend lowers a description onto the IR, the codegen resolves the IR, and a backend joins the resolved IR with the frontend’s physical-layout sidecar to emit code calling the wire runtime.
graph LR
subgraph IR["1. IR core"]
C["taktora-idl-core<br/>Module, Struct, Field,<br/>EnumDef, Service"]
end
subgraph Wire["2. Wire runtime"]
W["taktora-idl-wire<br/>WireType + bit-packing<br/>(no_std, no deps)"]
end
subgraph Front["3. Frontend"]
D["taktora-idl-dbc<br/>parse + lower →<br/>(Module, DbcLayout)"]
end
subgraph Gen["4. Codegen layer"]
G["taktora-idl-codegen<br/>naming + MessageBackend<br/>+ resolve / generate"]
end
subgraph Back["5. Backend + harness"]
B["taktora-idl-codegen-can<br/>CanBackend"]
T["taktora-idl-codegen-can-tests<br/>build-time round-trip"]
end
subgraph Cons["Consumers"]
U["generated module<br/>+ any runtime consumer"]
end
C --> D
C --> G
D --> B
G --> B
B --> T
W --> T
B -. emits code calling .-> W
W --> U
B --> U
|
The toolchain runs entirely at build time. A runtime consumer sees
only the generated module and links against |