-
Notifications
You must be signed in to change notification settings - Fork 8
Distributor Guide Writing Compilation Passes
This page explains how to write a new compilation pass that can be plugged into an RTIC distribution.
A compilation pass is a self-contained crate that implements a subset of RTIC functionality. It transforms RTIC syntax into RTIC syntax, usually simplifying the input so that the next pass or the core pass can understand it.
For example, the software-tasks pass transforms #[sw_task] and spawn() calls into hardware tasks and dispatcher interrupts. The deadline pass converts deadline = D into priority = N. The auto-assign pass infers core = N from shared resource usage.
Every pass implements RticPass from rtic-core:
use proc_macro2::TokenStream as TokenStream2;
use syn::ItemMod;
pub trait RticPass {
fn run_pass(
&self,
args: TokenStream2,
app_mod: ItemMod,
) -> syn::Result<(TokenStream2, ItemMod)>;
fn pass_name(&self) -> &str;
}-
args— the token stream of the#[rtic::app(...)]attribute arguments. -
app_mod— the annotated module. - The return value is the transformed
(args, app_mod). -
pass_nameis used in error messages to identify which pass failed.
Passes can be registered as pre-core or post-core passes:
use rtic_core::RticMacroBuilder;
let mut builder = RticMacroBuilder::new(my_backend);
builder.bind_pre_core_pass(MyPass);
builder.bind_post_core_pass(MyPostPass);- Pre-core passes run before
rtic-coreparses the module. Use them to expand high-level syntax into core RTIC syntax. - Post-core passes run after code generation. Use them to inspect or augment the generated token stream.
If a pass needs target-specific information, it can define its own backend trait. The distribution implements this trait and passes the implementation to the pass constructor.
For example, rtic-sw-pass defines SwPassBackend:
pub trait SwPassBackend {
fn generate_local_pend_fn(&self, empty_body_fn: ItemFn) -> ItemFn;
fn generate_cross_pend_fn(&self, empty_body_fn: ItemFn) -> Option<ItemFn>;
fn custom_interrupt_path(&self, core: u32) -> Option<syn::Path> { None }
}-
generate_local_pend_fnfills the body of the core-local interrupt-pending function used byspawn. -
generate_cross_pend_fnfills the cross-core interrupt-pending function used byspawn_from. ReturnsNoneon single-core targets. -
custom_interrupt_pathoptionally overrides the default PAC interrupt path.
A typical pass crate contains:
- A
Cargo.tomlwithrtic-coreas a dependency. - A public type implementing
RticPass. - Optionally, a public backend trait for target-specific hooks.
- A public constructor that accepts the backend trait implementation.
use proc_macro2::TokenStream as TokenStream2;
use syn::ItemMod;
use rtic_core::RticPass;
pub struct MyPass;
impl RticPass for MyPass {
fn run_pass(
&self,
args: TokenStream2,
mut app_mod: ItemMod,
) -> syn::Result<(TokenStream2, ItemMod)> {
// Inspect and transform app_mod and args here
Ok((args, app_mod))
}
fn pass_name(&self) -> &str {
"my-pass"
}
}Because passes are pure syntax transformers, you can test them by feeding them a parsed ItemMod and asserting on the output. For passes that change the module items, you can also write trybuild-style compilation tests as distribution examples.
- Writing Distributions — plug your pass into a distribution.
- Distributor Guide Architecture — understand the full pipeline.