Skip to content

Distributor Guide Architecture

Zakaria Madaoui edited this page Aug 12, 2026 · 11 revisions

Architecture

This page describes the modular architecture of RTICX and how the core framework, compilation passes, and distributions interact in addition to how multicore is supported.

Three layers

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  User application                           β”‚
β”‚  #[<distro>::app(...)]                      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Distribution                               β”‚
β”‚  - RticMacroBuilder                         β”‚
β”‚  - CorePassBackend impl                     β”‚
β”‚  - Selected passes (pre/post core)          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Compilation passes                         β”‚
β”‚  - RticPass implementations                 β”‚
β”‚  - Pure syntax-to-syntax transformations    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  rticx-core                                 β”‚
β”‚  - Parse #[<distro>::app]                   β”‚
β”‚  - Run SRP analysis                         β”‚
β”‚  - Generate code via CorePassBackend        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

rticx-core

rticx-core is the bottom layer. It is responsible for:

  • Parsing the #[<distro>::app] attribute and the annotated module into an AST (App).
  • Running resource-ceiling analysis under the Stack Resource Policy (SRP).
  • Generating the final Rust code for tasks, resources, init, idle, and interrupt dispatchers.

To keep rticx-core target-agnostic, the actual hardware-specific code generation is delegated to a backend trait.

CorePassBackend

CorePassBackend is the interface a distribution implements to provide hardware-specific details:

Method Purpose
post_init(...) Code inserted after init and task initialization.
generate_resource_proxy_lock_impl(...) Body of the lock function for shared resources.
generate_global_definitions(...) Extra constants, imports, or helpers at global scope.
wrap_task_execution(...) Wrap the task exec call inside an interrupt handler.
entry_name(core) Name of the entry function for each core.
populate_idle_loop() Custom body for the default idle loop.
generate_interrupt_free_fn(...) Implement the global critical-section function.
pre_codegen_validation(...) Target-specific validation before codegen.
default_task_priority() Fallback priority when omitted.
entry_attrs() Attributes injected onto entry points.
task_attrs(...) Attributes injected onto task interrupt handlers.

Compilation passes

A compilation pass is a crate that implements RticPass:

pub trait RticPass {
    fn run_pass(
        &self,
        args: TokenStream2,
        app_mod: ItemMod,
    ) -> syn::Result<(TokenStream2, ItemMod)>;

    fn pass_name(&self) -> &str;
}

Passes are pure syntax-to-syntax transformations. They receive the macro arguments and the annotated module, transform them, and return the updated pair. Passes are registered with RticMacroBuilder either before or after the core pass.

Pre-core passes

Run before the module is parsed by rticx-core. They are used to transform high-level syntax into the core RTICX syntax that the core pass understands. For example, the software-tasks pass converts #[sw_task] and spawn calls into hardware tasks and dispatcher queues.

Post-core passes

Run after the core code generation. They can inspect or augment the generated output.

RticMacroBuilder pipeline

Every distribution builds its macro by constructing an RticMacroBuilder:

let mut builder = RticMacroBuilder::new(my_backend);
builder.bind_pre_core_pass(SoftwarePass::new(my_sw_backend));
builder.bind_pre_core_pass(AutoAssignPass);
let tokens = builder.build_rtic_macro(args, input);

The pipeline order inside build_rtic_macro is:

  1. Reset the default task priority from the backend.
  2. Run pre-core passes in insertion order.
  3. Parse the module with App::parse(args, app_mod).
  4. Run SRP analysis.
  5. Call CorePassBackend::pre_codegen_validation.
  6. Run code generation via CodeGen::new(core_backend, &parsed_app, &analysis).run().
  7. Run post-core passes in insertion order.
  8. If debug_expand is enabled, write the expansion to examples/{binary_name}_expanded.rs.

Core compilation pass

TBA

Software Tasks pass

TBA

Async Tasks Pass

TBA

Multicore model

RTICX expands the original RTIC framework syntax and implementation to support multicore targets without sacrificing the SRP guarantees. It does that by first setting the following restrictions:

  • Each core has owns and manages its own set of shared resources (#[shared(core = N)]). This is in order to preserve SRP guarantees.

  • Software/Async tasks as the primary method of inter-core communication. One core can spawn a software task on the other core and pass arguments to it. these tasks are knowns as cross-core tasks.

  • cross-core tasks are constrained further to preserve SRP. A software tasks dispatcher can either manage core-local or cross-core tasks but not both. I.e, a priority line can be reserved for either core-local tasks or cross-core tasks. Furthermore, the user has to specify which core a cross-core task belongs to (core = N) and which other core will be able to spawn it (spawned_by = M). The reason for those two restrictions is that there can only be one producer and one consumer for the queues that are used by dispatchers to pass task arguments. In the case of core-local, the same core is both the consumer and producer. In cross-core case, one core is a producer and the other is consumer.

  • the pending of an interrupt from one core on another core is implementation specific and is managed by the distribution that enabled multicore support.

  • In some distributions, asynchronous channels (async feature) can also be used as another mean of cross-core communication if the distribution implements additional bindings which will be described in a later stage.

  • Currently multicore support in RTICX is restricted to homogeneous architectures where all cores share the same memory and only a single binary is required. This type of targets will be referred to as single-binary multicore targets. multi-binary multicore targets support is under work and will be released in the near future.

The core compilation pass generates per-core entry points, interrupt handlers, and shared resource proxies. The backend decides:

  • How each core is started (e.g., RP2040 starts core 1 from post_init backend binding).
  • If sw-pass or async-pass is enabled, how cross-core tasks are dispatched. (e.g RP2040 uses mailboxes for cross-core interrupt pending), and optionally how async executor's interrupt is pended by the waker.

More details are discussed in Multicore Architecture and Design Decisions page.

Next steps

Clone this wiki locally