aboutsummaryrefslogtreecommitdiff
path: root/macros/src/codegen.rs
diff options
context:
space:
mode:
Diffstat (limited to 'macros/src/codegen.rs')
-rw-r--r--macros/src/codegen.rs1815
1 files changed, 1815 insertions, 0 deletions
diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs
new file mode 100644
index 00000000..ff1062ae
--- /dev/null
+++ b/macros/src/codegen.rs
@@ -0,0 +1,1815 @@
+use proc_macro::TokenStream;
+use std::{
+ collections::HashMap,
+ sync::atomic::{AtomicUsize, Ordering},
+ time::{SystemTime, UNIX_EPOCH},
+};
+
+use proc_macro2::Span;
+use quote::quote;
+use rand::{Rng, SeedableRng};
+use syn::{ArgCaptured, Ident, IntSuffix, LitInt};
+
+use analyze::{Analysis, Ownership};
+use syntax::{App, Idents, Static};
+
+// NOTE to avoid polluting the user namespaces we map some identifiers to pseudo-hygienic names.
+// In some instances we also use the pseudo-hygienic names for safety, for example the user should
+// not modify the priority field of resources.
+type Aliases = HashMap<Ident, Ident>;
+
+struct Context {
+ // Alias
+ #[cfg(feature = "timer-queue")]
+ baseline: Ident,
+ // Dispatcher -> Alias (`enum`)
+ enums: HashMap<u8, Ident>,
+ // Task -> Alias (`static` / resource)
+ free_queues: Aliases,
+ // Alias (`fn`)
+ idle: Ident,
+ // Alias (`fn`)
+ init: Ident,
+ // Task -> Alias (`static`)
+ inputs: Aliases,
+ // Alias
+ priority: Ident,
+ // Dispatcher -> Alias (`static` / resource)
+ ready_queues: HashMap<u8, Ident>,
+ // For non-singletons this maps the resource name to its `static mut` variable name
+ statics: Aliases,
+ /// Task -> Alias (`struct`)
+ resources: HashMap<Kind, Resources>,
+ // Task -> Alias (`static`)
+ #[cfg(feature = "timer-queue")]
+ scheduleds: Aliases,
+ // Task -> Alias (`fn`)
+ spawn_fn: Aliases,
+ // Alias (`enum`)
+ schedule_enum: Ident,
+ // Task -> Alias (`fn`)
+ schedule_fn: Aliases,
+ tasks: Aliases,
+ // Alias (`struct` / `static mut`)
+ timer_queue: Ident,
+}
+
+impl Default for Context {
+ fn default() -> Self {
+ Context {
+ #[cfg(feature = "timer-queue")]
+ baseline: mk_ident(),
+ enums: HashMap::new(),
+ free_queues: Aliases::new(),
+ idle: mk_ident(),
+ init: mk_ident(),
+ inputs: Aliases::new(),
+ priority: mk_ident(),
+ ready_queues: HashMap::new(),
+ statics: Aliases::new(),
+ resources: HashMap::new(),
+ #[cfg(feature = "timer-queue")]
+ scheduleds: Aliases::new(),
+ spawn_fn: Aliases::new(),
+ schedule_enum: mk_ident(),
+ schedule_fn: Aliases::new(),
+ tasks: Aliases::new(),
+ timer_queue: mk_ident(),
+ }
+ }
+}
+
+struct Resources {
+ alias: Ident,
+ decl: proc_macro2::TokenStream,
+}
+
+pub fn app(app: &App, analysis: &Analysis) -> TokenStream {
+ let mut ctxt = Context::default();
+
+ let device = &app.args.device;
+
+ let resources = resources(&mut ctxt, &app, analysis);
+
+ let tasks = tasks(&mut ctxt, &app, analysis);
+
+ let (dispatchers_data, dispatchers) = dispatchers(&mut ctxt, &app, analysis);
+
+ let init_fn = init(&mut ctxt, &app, analysis);
+ let init_arg = if cfg!(feature = "timer-queue") {
+ quote!(rtfm::Peripherals {
+ CBP: p.CBP,
+ CPUID: p.CPUID,
+ DCB: &mut p.DCB,
+ FPB: p.FPB,
+ FPU: p.FPU,
+ ITM: p.ITM,
+ MPU: p.MPU,
+ SCB: &mut p.SCB,
+ TPIU: p.TPIU,
+ })
+ } else {
+ quote!(rtfm::Peripherals {
+ CBP: p.CBP,
+ CPUID: p.CPUID,
+ DCB: p.DCB,
+ DWT: p.DWT,
+ FPB: p.FPB,
+ FPU: p.FPU,
+ ITM: p.ITM,
+ MPU: p.MPU,
+ SCB: &mut p.SCB,
+ SYST: p.SYST,
+ TPIU: p.TPIU,
+ })
+ };
+
+ let post_init = post_init(&ctxt, &app, analysis);
+
+ let (idle_fn, idle_expr) = idle(&mut ctxt, &app, analysis);
+
+ let exceptions = exceptions(&mut ctxt, app, analysis);
+
+ let (root_interrupts, scoped_interrupts) = interrupts(&mut ctxt, app, analysis);
+
+ let spawn = spawn(&mut ctxt, app, analysis);
+
+ let schedule = match () {
+ #[cfg(feature = "timer-queue")]
+ () => schedule(&ctxt, app),
+ #[cfg(not(feature = "timer-queue"))]
+ () => quote!(),
+ };
+
+ let timer_queue = timer_queue(&ctxt, app, analysis);
+
+ let pre_init = pre_init(&ctxt, analysis);
+
+ let assertions = assertions(app, analysis);
+
+ let init = &ctxt.init;
+ quote!(
+ #resources
+
+ #spawn
+
+ #timer_queue
+
+ #schedule
+
+ #dispatchers_data
+
+ #(#exceptions)*
+
+ #root_interrupts
+
+ // We put these items into a pseudo-module to avoid a collision between the `interrupt`
+ // import and user code
+ const APP: () = {
+ use #device::interrupt;
+
+ #scoped_interrupts
+
+ #(#dispatchers)*
+ };
+
+ #(#tasks)*
+
+ #init_fn
+
+ #idle_fn
+
+ #[allow(unsafe_code)]
+ #[rtfm::export::entry]
+ #[doc(hidden)]
+ unsafe fn main() -> ! {
+ #assertions
+
+ rtfm::export::interrupt::disable();
+
+ #pre_init
+
+ #init(#init_arg);
+
+ #post_init
+
+ rtfm::export::interrupt::enable();
+
+ #idle_expr
+ }
+ )
+ .into()
+}
+
+fn resources(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream {
+ let mut items = vec![];
+ let mut module = vec![];
+ for (name, res) in &app.resources {
+ let attrs = &res.attrs;
+ let mut_ = &res.mutability;
+ let ty = &res.ty;
+ let expr = &res.expr;
+
+ if res.singleton {
+ items.push(quote!(
+ #(#attrs)*
+ pub static #mut_ #name: #ty = #expr;
+ ));
+
+ let alias = mk_ident();
+ if let Some(Ownership::Shared { ceiling }) = analysis.ownerships.get(name) {
+ items.push(mk_resource(
+ ctxt,
+ name,
+ quote!(#name),
+ *ceiling,
+ quote!(&mut <#name as owned_singleton::Singleton>::new()),
+ app,
+ Some(&mut module),
+ ))
+ }
+
+ ctxt.statics.insert(name.clone(), alias);
+ } else {
+ let alias = mk_ident();
+ let symbol = format!("{}::{}", name, alias);
+
+ items.push(
+ expr.as_ref()
+ .map(|expr| {
+ quote!(
+ #(#attrs)*
+ #[export_name = #symbol]
+ static mut #alias: #ty = #expr;
+ )
+ })
+ .unwrap_or_else(|| {
+ quote!(
+ #(#attrs)*
+ #[export_name = #symbol]
+ static mut #alias: rtfm::export::MaybeUninit<#ty> =
+ rtfm::export::MaybeUninit::uninitialized();
+ )
+ }),
+ );
+
+ if let Some(Ownership::Shared { ceiling }) = analysis.ownerships.get(name) {
+ if res.mutability.is_some() {
+ let ptr = if res.expr.is_none() {
+ quote!(unsafe { #alias.get_mut() })
+ } else {
+ quote!(unsafe { &mut #alias })
+ };
+
+ items.push(mk_resource(
+ ctxt,
+ name,
+ quote!(#ty),
+ *ceiling,
+ ptr,
+ app,
+ Some(&mut module),
+ ));
+ }
+ }
+
+ ctxt.statics.insert(name.clone(), alias);
+ }
+ }
+
+ if !module.is_empty() {
+ items.push(quote!(
+ /// Resource proxies
+ pub mod resources {
+ #(#module)*
+ }));
+ }
+
+ quote!(#(#items)*)
+}
+
+fn init(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream {
+ let attrs = &app.init.attrs;
+ let locals = mk_locals(&app.init.statics, true);
+ let stmts = &app.init.stmts;
+ let assigns = app
+ .init
+ .assigns
+ .iter()
+ .map(|assign| {
+ if app
+ .resources
+ .get(&assign.left)
+ .map(|r| r.expr.is_none())
+ .unwrap_or(false)
+ {
+ let alias = &ctxt.statics[&assign.left];
+ let expr = &assign.right;
+ quote!(unsafe { #alias.set(#expr); })
+ } else {
+ let left = &assign.left;
+ let right = &assign.right;
+ quote!(#left = #right;)
+ }
+ })
+ .collect::<Vec<_>>();
+
+ let prelude = prelude(
+ ctxt,
+ Kind::Init,
+ &app.init.args.resources,
+ &app.init.args.spawn,
+ &app.init.args.schedule,
+ app,
+ 255,
+ analysis,
+ );
+
+ let module = module(
+ ctxt,
+ Kind::Init,
+ !app.init.args.schedule.is_empty(),
+ !app.init.args.spawn.is_empty(),
+ app,
+ );
+
+ #[cfg(feature = "timer-queue")]
+ let baseline = &ctxt.baseline;
+ let baseline_let = match () {
+ #[cfg(feature = "timer-queue")]
+ () => quote!(let #baseline = rtfm::Instant::artificial(0);),
+
+ #[cfg(not(feature = "timer-queue"))]
+ () => quote!(),
+ };
+
+ let start_let = match () {
+ #[cfg(feature = "timer-queue")]
+ () => quote!(
+ #[allow(unused_variables)]
+ let start = #baseline;
+ ),
+ #[cfg(not(feature = "timer-queue"))]
+ () => quote!(),
+ };
+
+ let unsafety = &app.init.unsafety;
+ let device = &app.args.device;
+ let init = &ctxt.init;
+ let name = format!("init::{}", init);
+ quote!(
+ #module
+
+ #(#attrs)*
+ #[export_name = #name]
+ #unsafety fn #init(mut core: rtfm::Peripherals) {
+ #(#locals)*
+
+ #baseline_let
+
+ #prelude
+
+ let mut device = unsafe { #device::Peripherals::steal() };
+
+ #start_let
+
+ #(#stmts)*
+
+ #(#assigns)*
+ }
+ )
+}
+
+fn post_init(ctxt: &Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream {
+ let mut exprs = vec![];
+
+ // TODO turn the assertions that check that the priority is not larger than what's supported by
+ // the device into compile errors
+ let device = &app.args.device;
+ let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS);
+ for (name, interrupt) in &app.interrupts {
+ let priority = interrupt.args.priority;
+ exprs.push(quote!(p.NVIC.enable(#device::Interrupt::#name)));
+ exprs.push(quote!(assert!(#priority <= (1 << #nvic_prio_bits))));
+ exprs.push(quote!(p.NVIC.set_priority(
+ #device::Interrupt::#name,
+ ((1 << #nvic_prio_bits) - #priority) << (8 - #nvic_prio_bits),
+ )));
+ }
+
+ for (name, exception) in &app.exceptions {
+ let priority = exception.args.priority;
+ exprs.push(quote!(assert!(#priority <= (1 << #nvic_prio_bits))));
+ exprs.push(quote!(p.SCB.set_priority(
+ rtfm::export::SystemHandler::#name,
+ ((1 << #nvic_prio_bits) - #priority) << (8 - #nvic_prio_bits),
+ )));
+ }
+
+ if !analysis.timer_queue.tasks.is_empty() {
+ let priority = analysis.timer_queue.priority;
+ exprs.push(quote!(assert!(#priority <= (1 << #nvic_prio_bits))));
+ exprs.push(quote!(p.SCB.set_priority(
+ rtfm::export::SystemHandler::SysTick,
+ ((1 << #nvic_prio_bits) - #priority) << (8 - #nvic_prio_bits),
+ )));
+ }
+
+ for (priority, dispatcher) in &analysis.dispatchers {
+ let name = &dispatcher.interrupt;
+ exprs.push(quote!(p.NVIC.enable(#device::Interrupt::#name)));
+ exprs.push(quote!(assert!(#priority <= (1 << #nvic_prio_bits))));
+ exprs.push(quote!(p.NVIC.set_priority(
+ #device::Interrupt::#name,
+ ((1 << #nvic_prio_bits) - #priority) << (8 - #nvic_prio_bits),
+ )));
+ }
+
+ if app.idle.is_none() {
+ // Set SLEEPONEXIT bit to enter sleep mode when returning from ISR
+ exprs.push(quote!(p.SCB.scr.modify(|r| r | 1 << 1)));
+ }
+
+ // Enable and start the system timer
+ if !analysis.timer_queue.tasks.is_empty() {
+ let tq = &ctxt.timer_queue;
+ exprs.push(quote!(#tq.get_mut().syst.set_clock_source(rtfm::export::SystClkSource::Core)));
+ exprs.push(quote!(#tq.get_mut().syst.enable_counter()));
+ }
+
+ // Enable cycle counter
+ if cfg!(feature = "timer-queue") {
+ exprs.push(quote!(p.DCB.enable_trace()));
+ exprs.push(quote!(p.DWT.enable_cycle_counter()));
+ }
+
+ quote!(#(#exprs;)*)
+}
+
+/// This function creates creates a module for `init` / `idle` / a `task` (see `kind` argument)
+fn module(
+ ctxt: &mut Context,
+ kind: Kind,
+ schedule: bool,
+ spawn: bool,
+ app: &App,
+) -> proc_macro2::TokenStream {
+ let mut items = vec![];
+ let mut fields = vec![];
+
+ let name = kind.ident();
+ let priority = &ctxt.priority;
+ let device = &app.args.device;
+
+ let mut lt = None;
+ match kind {
+ Kind::Init => {
+ if cfg!(feature = "timer-queue") {
+ fields.push(quote!(
+ /// System start time = `Instant(0 /* cycles */)`
+ pub start: rtfm::Instant,
+ ));
+ }
+
+ fields.push(quote!(
+ /// Core (Cortex-M) peripherals
+ pub core: rtfm::Peripherals<'a>,
+ /// Device specific peripherals
+ pub device: #device::Peripherals,
+ ));
+ lt = Some(quote!('a));
+ }
+ Kind::Idle => {}
+ Kind::Exception(_) | Kind::Interrupt(_) => {
+ if cfg!(feature = "timer-queue") {
+ fields.push(quote!(
+ /// Time at which this handler started executing
+ pub start: rtfm::Instant,
+ ));
+ }
+ }
+ Kind::Task(_) => {
+ if cfg!(feature = "timer-queue") {
+ fields.push(quote!(
+ /// The time at which this task was scheduled to run
+ pub scheduled: rtfm::Instant,
+ ));
+ }
+ }
+ }
+
+ if schedule {
+ lt = Some(quote!('a));
+
+ fields.push(quote!(
+ /// Tasks that can be scheduled from this context
+ pub schedule: Schedule<'a>,
+ ));
+
+ items.push(quote!(
+ /// Tasks that can be scheduled from this context
+ #[derive(Clone, Copy)]
+ pub struct Schedule<'a> {
+ #[doc(hidden)]
+ pub #priority: &'a core::cell::Cell<u8>,
+ }
+ ));
+ }
+
+ if spawn {
+ lt = Some(quote!('a));
+
+ fields.push(quote!(
+ /// Tasks that can be spawned from this context
+ pub spawn: Spawn<'a>,
+ ));
+
+ if kind.is_idle() {
+ items.push(quote!(
+ /// Tasks that can be spawned from this context
+ #[derive(Clone, Copy)]
+ pub struct Spawn<'a> {
+ #[doc(hidden)]
+ pub #priority: &'a core::cell::Cell<u8>,
+ }
+ ));
+ } else {
+ let baseline_field = match () {
+ #[cfg(feature = "timer-queue")]
+ () => {
+ let baseline = &ctxt.baseline;
+ quote!(
+ #[doc(hidden)]
+ pub #baseline: rtfm::Instant,
+ )
+ }
+ #[cfg(not(feature = "timer-queue"))]
+ () => quote!(),
+ };
+
+ items.push(quote!(
+ /// Tasks that can be spawned from this context
+ #[derive(Clone, Copy)]
+ pub struct Spawn<'a> {
+ #baseline_field
+ #[doc(hidden)]
+ pub #priority: &'a core::cell::Cell<u8>,
+ }
+ ));
+ }
+ }
+
+ let mut root = None;
+ if let Some(resources) = ctxt.resources.get(&kind) {
+ lt = Some(quote!('a));
+
+ root = Some(resources.decl.clone());
+
+ let alias = &resources.alias;
+ items.push(quote!(
+ #[doc(inline)]
+ pub use super::#alias as Resources;
+ ));
+
+ fields.push(quote!(
+ /// Resources available in this context
+ pub resources: Resources<'a>,
+ ));
+ };
+
+ let doc = match kind {
+ Kind::Exception(_) => "Exception handler",
+ Kind::Idle => "Idle loop",
+ Kind::Init => "Initialization function",
+ Kind::Interrupt(_) => "Interrupt handler",
+ Kind::Task(_) => "Software task",
+ };
+
+ quote!(
+ #root
+
+ #[doc = #doc]
+ pub mod #name {
+ /// Variables injected into this context by the `app` attribute
+ pub struct Context<#lt> {
+ #(#fields)*
+ }
+
+ #(#items)*
+ }
+ )
+}
+
+/// The prelude injects `resources`, `spawn`, `schedule` and `start` / `scheduled` (all values) into
+/// a function scope
+fn prelude(
+ ctxt: &mut Context,
+ kind: Kind,
+ resources: &Idents,
+ spawn: &Idents,
+ schedule: &Idents,
+ app: &App,
+ logical_prio: u8,
+ analysis: &Analysis,
+) -> proc_macro2::TokenStream {
+ let mut items = vec![];
+
+ let lt = if kind.runs_once() {
+ quote!('static)
+ } else {
+ quote!('a)
+ };
+
+ let module = kind.ident();
+
+ let priority = &ctxt.priority;
+ if !resources.is_empty() {
+ let mut defs = vec![];
+ let mut exprs = vec![];
+
+ // NOTE This field is just to avoid unused type parameter errors around `'a`
+ defs.push(quote!(#[allow(dead_code)] #priority: &'a core::cell::Cell<u8>));
+ exprs.push(quote!(#priority));
+
+ let mut may_call_lock = false;
+ let mut needs_unsafe = false;
+ for name in resources {
+ let res = &app.resources[name];
+ let initialized = res.expr.is_some();
+ let singleton = res.singleton;
+ let mut_ = res.mutability;
+ let ty = &res.ty;
+
+ if kind.is_init() {
+ let mut force_mut = false;
+ if !analysis.ownerships.contains_key(name) {
+ // owned by Init
+ if singleton {
+ needs_unsafe = true;
+ defs.push(quote!(pub #name: #name));
+ exprs.push(quote!(#name: <#name as owned_singleton::Singleton>::new()));
+ continue;
+ } else {
+ defs.push(quote!(pub #name: &'static #mut_ #ty));
+ }
+ } else {
+ // owned by someone else
+ if singleton {
+ needs_unsafe = true;
+ defs.push(quote!(pub #name: &'a mut #name));
+ exprs
+ .push(quote!(#name: &mut <#name as owned_singleton::Singleton>::new()));
+ continue;
+ } else {
+ force_mut = true;
+ defs.push(quote!(pub #name: &'a mut #ty));
+ }
+ }
+
+ let alias = &ctxt.statics[name];
+ // Resources assigned to init are always const initialized
+ needs_unsafe = true;
+ if force_mut {
+ exprs.push(quote!(#name: &mut #alias));
+ } else {
+ exprs.push(quote!(#name: &#mut_ #alias));
+ }
+ } else {
+ let ownership = &analysis.ownerships[name];
+
+ if ownership.needs_lock(logical_prio) {
+ may_call_lock = true;
+ if singleton {
+ if mut_.is_none() {
+ needs_unsafe = true;
+ defs.push(quote!(pub #name: &'a #name));
+ exprs
+ .push(quote!(#name: &<#name as owned_singleton::Singleton>::new()));
+ continue;
+ } else {
+ // Generate a resource proxy
+ defs.push(quote!(pub #name: resources::#name<'a>));
+ exprs.push(quote!(#name: resources::#name { #priority }));
+ continue;
+ }
+ } else {
+ if mut_.is_none() {
+ defs.push(quote!(pub #name: &'a #ty));
+ } else {
+ // Generate a resource proxy
+ defs.push(quote!(pub #name: resources::#name<'a>));
+ exprs.push(quote!(#name: resources::#name { #priority }));
+ continue;
+ }
+ }
+ } else {
+ if singleton {
+ if kind.runs_once() {
+ needs_unsafe = true;
+ defs.push(quote!(pub #name: #name));
+ exprs.push(quote!(#name: <#name as owned_singleton::Singleton>::new()));
+ } else {
+ needs_unsafe = true;
+ defs.push(quote!(pub #name: &'a mut #name));
+ exprs.push(
+ quote!(#name: &mut <#name as owned_singleton::Singleton>::new()),
+ );
+ }
+ continue;
+ } else {
+ defs.push(quote!(pub #name: &#lt #mut_ #ty));
+ }
+ }
+
+ let alias = &ctxt.statics[name];
+ needs_unsafe = true;
+ if initialized {
+ exprs.push(quote!(#name: &#mut_ #alias));
+ } else {
+ let method = if mut_.is_some() {
+ quote!(get_mut)
+ } else {
+ quote!(get_ref)
+ };
+ exprs.push(quote!(#name: #alias.#method() ));
+ }
+ }
+ }
+
+ let alias = mk_ident();
+ let unsafety = if needs_unsafe {
+ Some(quote!(unsafe))
+ } else {
+ None
+ };
+
+ let doc = format!("`{}::Resources`", kind.ident().to_string());
+ let decl = quote!(
+ #[doc = #doc]
+ #[allow(non_snake_case)]
+ pub struct #alias<'a> { #(#defs,)* }
+ );
+ items.push(quote!(
+ #[allow(unused_variables)]
+ #[allow(unsafe_code)]
+ #[allow(unused_mut)]
+ let mut resources = #unsafety { #alias { #(#exprs,)* } };
+ ));
+
+ ctxt.resources
+ .insert(kind.clone(), Resources { alias, decl });
+
+ if may_call_lock {
+ items.push(quote!(
+ use rtfm::Mutex;
+ ));
+ }
+ }
+
+ if !spawn.is_empty() {
+ // Populate `spawn_fn`
+ for task in spawn {
+ if ctxt.spawn_fn.contains_key(task) {
+ continue;
+ }
+
+ ctxt.spawn_fn.insert(task.clone(), mk_ident());
+ }
+
+ if kind.is_idle() {
+ items.push(quote!(
+ #[allow(unused_variables)]
+ let spawn = #module::Spawn { #priority };
+ ));
+ } else {
+ let baseline_expr = match () {
+ #[cfg(feature = "timer-queue")]
+ () => {
+ let baseline = &ctxt.baseline;
+ quote!(#baseline)
+ }
+ #[cfg(not(feature = "timer-queue"))]
+ () => quote!(),
+ };
+ items.push(quote!(
+ #[allow(unused_variables)]
+ let spawn = #module::Spawn { #priority, #baseline_expr };
+ ));
+ }
+ }
+
+ if !schedule.is_empty() {
+ // Populate `schedule_fn`
+ for task in schedule {
+ if ctxt.schedule_fn.contains_key(task) {
+ continue;
+ }
+
+ ctxt.schedule_fn.insert(task.clone(), mk_ident());
+ }
+
+ items.push(quote!(
+ #[allow(unused_imports)]
+ use rtfm::U32Ext;
+
+ #[allow(unused_variables)]
+ let schedule = #module::Schedule { #priority };
+ ));
+ }
+
+ if items.is_empty() {
+ quote!()
+ } else {
+ quote!(
+ let ref #priority = core::cell::Cell::new(#logical_prio);
+
+ #(#items)*
+ )
+ }
+}
+
+fn idle(
+ ctxt: &mut Context,
+ app: &App,
+ analysis: &Analysis,
+) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
+ if let Some(idle) = app.idle.as_ref() {
+ let attrs = &idle.attrs;
+ let locals = mk_locals(&idle.statics, true);
+ let stmts = &idle.stmts;
+
+ let prelude = prelude(
+ ctxt,
+ Kind::Idle,
+ &idle.args.resources,
+ &idle.args.spawn,
+ &idle.args.schedule,
+ app,
+ 0,
+ analysis,
+ );
+
+ let module = module(
+ ctxt,
+ Kind::Idle,
+ !idle.args.schedule.is_empty(),
+ !idle.args.spawn.is_empty(),
+ app,
+ );
+
+ let unsafety = &idle.unsafety;
+ let idle = &ctxt.idle;
+
+ let name = format!("idle::{}", idle);
+ (
+ quote!(
+ #module
+
+ #(#attrs)*
+ #[export_name = #name]
+ #unsafety fn #idle() -> ! {
+ #(#locals)*
+
+ #prelude
+
+ #(#stmts)*
+ }),
+ quote!(#idle()),
+ )
+ } else {
+ (
+ quote!(),
+ quote!(loop {
+ rtfm::export::wfi();
+ }),
+ )
+ }
+}
+
+fn exceptions(ctxt: &mut Context, app: &App, analysis: &Analysis) -> Vec<proc_macro2::TokenStream> {
+ app.exceptions
+ .iter()
+ .map(|(ident, exception)| {
+ let attrs = &exception.attrs;
+ let statics = &exception.statics;
+ let stmts = &exception.stmts;
+
+ let prelude = prelude(
+ ctxt,
+ Kind::Exception(ident.clone()),
+ &exception.args.resources,
+ &exception.args.spawn,
+ &exception.args.schedule,
+ app,
+ exception.args.priority,
+ analysis,
+ );
+
+ let module = module(
+ ctxt,
+ Kind::Exception(ident.clone()),
+ !exception.args.schedule.is_empty(),
+ !exception.args.spawn.is_empty(),
+ app,
+ );
+
+ #[cfg(feature = "timer-queue")]
+ let baseline = &ctxt.baseline;
+ let baseline_let = match () {
+ #[cfg(feature = "timer-queue")]
+ () => quote!(let #baseline = rtfm::Instant::now();),
+ #[cfg(not(feature = "timer-queue"))]
+ () => quote!(),
+ };
+
+ let start_let = match () {
+ #[cfg(feature = "timer-queue")]
+ () => quote!(
+ #[allow(unused_variables)]
+ let start = #baseline;
+ ),
+ #[cfg(not(feature = "timer-queue"))]
+ () => quote!(),
+ };
+
+ let unsafety = &exception.unsafety;
+ quote!(
+ #module
+
+ #[rtfm::export::exception]
+ #[doc(hidden)]
+ #(#attrs)*
+ #unsafety fn #ident() {
+ #(#statics)*
+
+ #baseline_let
+
+ #prelude
+
+ #start_let
+
+ rtfm::export::run(move || {
+ #(#stmts)*
+ })
+ })
+ })
+ .collect()
+}
+
+fn interrupts(
+ ctxt: &mut Context,
+ app: &App,
+ analysis: &Analysis,
+) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
+ let mut root = vec![];
+ let mut scoped = vec![];
+
+ for (ident, interrupt) in &app.interrupts {
+ let attrs = &interrupt.attrs;
+ let statics = &interrupt.statics;
+ let stmts = &interrupt.stmts;
+
+ let prelude = prelude(
+ ctxt,
+ Kind::Interrupt(ident.clone()),
+ &interrupt.args.resources,
+ &interrupt.args.spawn,
+ &interrupt.args.schedule,
+ app,
+ interrupt.args.priority,
+ analysis,
+ );
+
+ root.push(module(
+ ctxt,
+ Kind::Interrupt(ident.clone()),
+ !interrupt.args.schedule.is_empty(),
+ !interrupt.args.spawn.is_empty(),
+ app,
+ ));
+
+ #[cfg(feature = "timer-queue")]
+ let baseline = &ctxt.baseline;
+ let baseline_let = match () {
+ #[cfg(feature = "timer-queue")]
+ () => quote!(let #baseline = rtfm::Instant::now();),
+ #[cfg(not(feature = "timer-queue"))]
+ () => quote!(),
+ };
+
+ let start_let = match () {
+ #[cfg(feature = "timer-queue")]
+ () => quote!(
+ #[allow(unused_variables)]
+ let start = #baseline;
+ ),
+ #[cfg(not(feature = "timer-queue"))]
+ () => quote!(),
+ };
+
+ let unsafety = &interrupt.unsafety;
+ scoped.push(quote!(
+ #[interrupt]
+ #(#attrs)*
+ #unsafety fn #ident() {
+ #(#statics)*
+
+ #baseline_let
+
+ #prelude
+
+ #start_let
+
+ rtfm::export::run(move || {
+ #(#stmts)*
+ })
+ }));
+ }
+
+ (quote!(#(#root)*), quote!(#(#scoped)*))
+}
+
+fn tasks(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream {
+ let mut items = vec![];
+ for (name, task) in &app.tasks {
+ #[cfg(feature = "timer-queue")]
+ let scheduleds_alias = mk_ident();
+ let free_alias = mk_ident();
+ let inputs_alias = mk_ident();
+ let task_alias = mk_ident();
+
+ let attrs = &task.attrs;
+ let inputs = &task.inputs;
+ let locals = mk_locals(&task.statics, false);
+ let stmts = &task.stmts;
+
+ let prelude = prelude(
+ ctxt,
+ Kind::Task(name.clone()),
+ &task.args.resources,
+ &task.args.spawn,
+ &task.args.schedule,
+ app,
+ task.args.priority,
+ analysis,
+ );
+
+ let ty = tuple_ty(inputs);
+
+ let capacity_lit = mk_capacity_literal(analysis.capacities[name]);
+ let capacity_ty = mk_typenum_capacity(analysis.capacities[name], true);
+
+ let resource = mk_resource(
+ ctxt,
+ &free_alias,
+ quote!(rtfm::export::FreeQueue<#capacity_ty>),
+ *analysis.free_queues.get(name).unwrap_or(&0),
+ quote!(#free_alias.get_mut()),
+ app,
+ None,
+ );
+
+ let scheduleds_static = match () {
+ #[cfg(feature = "timer-queue")]
+ () => {
+ let scheduleds_symbol = format!("{}::SCHEDULED_TIMES::{}", name, scheduleds_alias);
+
+ quote!(
+ #[export_name = #scheduleds_symbol]
+ static mut #scheduleds_alias:
+ rtfm::export::MaybeUninit<[rtfm::Instant; #capacity_lit]> =
+ rtfm::export::MaybeUninit::uninitialized();
+ )
+ }
+ #[cfg(not(feature = "timer-queue"))]
+ () => quote!(),
+ };
+
+ let scheduled_let = match () {
+ #[cfg(feature = "timer-queue")]
+ () => {
+ let baseline = &ctxt.baseline;
+ quote!(let scheduled = #baseline;)
+ }
+ #[cfg(not(feature = "timer-queue"))]
+ () => quote!(),
+ };
+
+ let baseline_arg = match () {
+ #[cfg(feature = "timer-queue")]
+ () => {
+ let baseline = &ctxt.baseline;
+ quote!(#baseline: rtfm::Instant,)
+ }
+ #[cfg(not(feature = "timer-queue"))]
+ () => quote!(),
+ };
+ let task_symbol = format!("{}::{}", name, task_alias);
+ let inputs_symbol = format!("{}::INPUTS::{}", name, inputs_alias);
+ let free_symbol = format!("{}::FREE_QUEUE::{}", name, free_alias);
+ let unsafety = &task.unsafety;
+ items.push(quote!(
+ // FIXME(MaybeUninit) MaybeUninit won't be necessary when core::mem::MaybeUninit
+ // stabilizes because heapless constructors will work in const context
+ #[export_name = #free_symbol]
+ static mut #free_alias: rtfm::export::MaybeUninit<
+ rtfm::export::FreeQueue<#capacity_ty>
+ > = rtfm::export::MaybeUninit::uninitialized();
+
+ #resource
+
+ #[export_name = #inputs_symbol]
+ static mut #inputs_alias: rtfm::export::MaybeUninit<[#ty; #capacity_lit]> =
+ rtfm::export::MaybeUninit::uninitialized();
+
+ #scheduleds_static
+
+ #(#attrs)*
+ #[export_name = #task_symbol]
+ #unsafety fn #task_alias(#baseline_arg #(#inputs,)*) {
+ #(#locals)*
+
+ #prelude
+
+ #scheduled_let
+
+ #(#stmts)*
+ }
+ ));
+
+ items.push(module(
+ ctxt,
+ Kind::Task(name.clone()),
+ !task.args.schedule.is_empty(),
+ !task.args.spawn.is_empty(),
+ app,
+ ));
+
+ #[cfg(feature = "timer-queue")]
+ ctxt.scheduleds.insert(name.clone(), scheduleds_alias);
+ ctxt.free_queues.insert(name.clone(), free_alias);
+ ctxt.inputs.insert(name.clone(), inputs_alias);
+ ctxt.tasks.insert(name.clone(), task_alias);
+ }
+
+ quote!(#(#items)*)
+}
+
+fn dispatchers(
+ ctxt: &mut Context,
+ app: &App,
+ analysis: &Analysis,
+) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
+ let mut data = vec![];
+ let mut dispatchers = vec![];
+
+ for (level, dispatcher) in &analysis.dispatchers {
+ let ready_alias = mk_ident();
+ let enum_alias = mk_ident();
+ let tasks = &dispatcher.tasks;
+ let capacity = mk_typenum_capacity(dispatcher.capacity, true);
+
+ let symbol = format!("P{}::READY_QUEUE::{}", level, ready_alias);
+ let e = quote!(rtfm::export);
+ let ty = quote!(#e::ReadyQueue<#enum_alias, #capacity>);
+ let ceiling = *analysis.ready_queues.get(&level).unwrap_or(&0);
+ let resource = mk_resource(
+ ctxt,
+ &ready_alias,
+ ty.clone(),
+ ceiling,
+ quote!(#ready_alias.get_mut()),
+ app,
+ None,
+ );
+ data.push(quote!(
+ #[allow(dead_code)]
+ #[allow(non_camel_case_types)]
+ enum #enum_alias { #(#tasks,)* }
+
+ #[export_name = #symbol]
+ static mut #ready_alias: #e::MaybeUninit<#ty> = #e::MaybeUninit::uninitialized();
+
+ #resource
+ ));
+
+ let interrupt = &dispatcher.interrupt;
+
+ let arms = dispatcher
+ .tasks
+ .iter()
+ .map(|task| {
+ let inputs = &ctxt.inputs[task];
+ let free = &ctxt.free_queues[task];
+ let pats = tuple_pat(&app.tasks[task].inputs);
+ let alias = &ctxt.tasks[task];
+
+ let baseline_let;
+ let call;
+ match () {
+ #[cfg(feature = "timer-queue")]
+ () => {
+ let scheduleds = &ctxt.scheduleds[task];
+ baseline_let = quote!(
+ let baseline =
+ ptr::read(#scheduleds.get_ref().get_unchecked(usize::from(index)));
+ );
+ call = quote!(#alias(baseline, #pats));
+ }
+ #[cfg(not(feature = "timer-queue"))]
+ () => {
+ baseline_let = quote!();
+ call = quote!(#alias(#pats));
+ }
+ };
+
+ quote!(#enum_alias::#task => {
+ #baseline_let
+ let input = ptr::read(#inputs.get_ref().get_unchecked(usize::from(index)));
+ #free.get_mut().split().0.enqueue_unchecked(index);
+ let (#pats) = input;
+ #call
+ })
+ })
+ .collect::<Vec<_>>();
+
+ let attrs = &dispatcher.attrs;
+ dispatchers.push(quote!(
+ #(#attrs)*
+ #[interrupt]
+ unsafe fn #interrupt() {
+ use core::ptr;
+
+ rtfm::export::run(|| {
+ while let Some((task, index)) = #ready_alias.get_mut().split().1.dequeue() {
+ match task {
+ #(#arms)*
+ }
+ }
+ });
+ }
+ ));
+
+ ctxt.ready_queues.insert(*level, ready_alias);
+ ctxt.enums.insert(*level, enum_alias);
+ }
+
+ (quote!(#(#data)*), quote!(#(#dispatchers)*))
+}
+
+fn spawn(ctxt: &Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream {
+ let mut items = vec![];
+
+ // Generate `spawn` functions
+ let device = &app.args.device;
+ let priority = &ctxt.priority;
+ #[cfg(feature = "timer-queue")]
+ let baseline = &ctxt.baseline;
+ for (task, alias) in &ctxt.spawn_fn {
+ let free = &ctxt.free_queues[task];
+ let level = app.tasks[task].args.priority;
+ let ready = &ctxt.ready_queues[&level];
+ let enum_ = &ctxt.enums[&level];
+ let dispatcher = &analysis.dispatchers[&level].interrupt;
+ let inputs = &ctxt.inputs[task];
+ let args = &app.tasks[task].inputs;
+ let ty = tuple_ty(args);
+ let pats = tuple_pat(args);
+
+ let scheduleds_write = match () {
+ #[cfg(feature = "timer-queue")]
+ () => {
+ let scheduleds = &ctxt.scheduleds[task];
+ quote!(
+ ptr::write(
+ #scheduleds.get_mut().get_unchecked_mut(usize::from(index)),
+ #baseline,
+ );
+ )
+ }
+ #[cfg(not(feature = "timer-queue"))]
+ () => quote!(),
+ };
+
+ let baseline_arg = match () {
+ #[cfg(feature = "timer-queue")]
+ () => quote!(#baseline: rtfm::Instant,),
+ #[cfg(not(feature = "timer-queue"))]
+ () => quote!(),
+ };
+
+ items.push(quote!(
+ #[inline(always)]
+ unsafe fn #alias(
+ #baseline_arg
+ #priority: &core::cell::Cell<u8>,
+ #(#args,)*
+ ) -> Result<(), #ty> {
+ use core::ptr;
+
+ use rtfm::Mutex;
+
+ if let Some(index) = (#free { #priority }).lock(|f| f.split().1.dequeue()) {
+ ptr::write(#inputs.get_mut().get_unchecked_mut(usize::from(index)), (#pats));
+ #scheduleds_write
+
+ #ready { #priority }.lock(|rq| {
+ rq.split().0.enqueue_unchecked((#enum_::#task, index))
+ });
+
+ rtfm::pend(#device::Interrupt::#dispatcher);
+
+ Ok(())
+ } else {
+ Err((#pats))
+ }
+ }
+ ))
+ }
+
+ // Generate `spawn` structs; these call the `spawn` functions generated above
+ for (name, spawn) in app.spawn_callers() {
+ if spawn.is_empty() {
+ continue;
+ }
+
+ #[cfg(feature = "timer-queue")]
+ let is_idle = name.to_string() == "idle";
+
+ let mut methods = vec![];
+ for task in spawn {
+ let alias = &ctxt.spawn_fn[task];
+ let inputs = &app.tasks[task].inputs;
+ let ty = tuple_ty(inputs);
+ let pats = tuple_pat(inputs);
+
+ let instant = match () {
+ #[cfg(feature = "timer-queue")]
+ () => {
+ if is_idle {
+ quote!(rtfm::Instant::now(),)
+ } else {
+ quote!(self.#baseline,)
+ }
+ }
+ #[cfg(not(feature = "timer-queue"))]
+ () => quote!(),
+ };
+ methods.push(quote!(
+ #[allow(unsafe_code)]
+ #[inline]
+ pub fn #task(&self, #(#inputs,)*) -> Result<(), #ty> {
+ unsafe { #alias(#instant &self.#priority, #pats) }
+ }
+ ));
+ }
+
+ items.push(quote!(
+ impl<'a> #name::Spawn<'a> {
+ #(#methods)*
+ }
+ ));
+ }
+
+ quote!(#(#items)*)
+}
+
+#[cfg(feature = "timer-queue")]
+fn schedule(ctxt: &Context, app: &App) -> proc_macro2::TokenStream {
+ let mut items = vec![];
+
+ // Generate `schedule` functions
+ let priority = &ctxt.priority;
+ let timer_queue = &ctxt.timer_queue;
+ for (task, alias) in &ctxt.schedule_fn {
+ let free = &ctxt.free_queues[task];
+ let enum_ = &ctxt.schedule_enum;
+ let inputs = &ctxt.inputs[task];
+ let scheduleds = &ctxt.scheduleds[task];
+ let args = &app.tasks[task].inputs;
+ let ty = tuple_ty(args);
+ let pats = tuple_pat(args);
+
+ items.push(quote!(
+ #[inline(always)]
+ unsafe fn #alias(
+ #priority: &core::cell::Cell<u8>,
+ instant: rtfm::Instant,
+ #(#args,)*
+ ) -> Result<(), #ty> {
+ use core::ptr;
+
+ use rtfm::Mutex;
+
+ if let Some(index) = (#free { #priority }).lock(|f| f.split().1.dequeue()) {
+ ptr::write(#inputs.get_mut().get_unchecked_mut(usize::from(index)), (#pats));
+ ptr::write(
+ #scheduleds.get_mut().get_unchecked_mut(usize::from(index)),
+ instant,
+ );
+
+ let nr = rtfm::export::NotReady {
+ instant,
+ index,
+ task: #enum_::#task,
+ };
+
+ ({#timer_queue { #priority }}).lock(|tq| tq.enqueue_unchecked(nr));
+
+ Ok(())
+ } else {
+ Err((#pats))
+ }
+ }
+ ))
+ }
+
+ // Generate `Schedule` structs; these call the `schedule` functions generated above
+ for (name, schedule) in app.schedule_callers() {
+ if schedule.is_empty() {
+ continue;
+ }
+
+ debug_assert!(!schedule.is_empty());
+
+ let mut methods = vec![];
+ for task in schedule {
+ let alias = &ctxt.schedule_fn[task];
+ let inputs = &app.tasks[task].inputs;
+ let ty = tuple_ty(inputs);
+ let pats = tuple_pat(inputs);
+
+ methods.push(quote!(
+ #[inline]
+ pub fn #task(
+ &self,
+ instant: rtfm::Instant,
+ #(#inputs,)*
+ ) -> Result<(), #ty> {
+ unsafe { #alias(&self.#priority, instant, #pats) }
+ }
+ ));
+ }
+
+ items.push(quote!(
+ impl<'a> #name::Schedule<'a> {
+ #(#methods)*
+ }
+ ));
+ }
+
+ quote!(#(#items)*)
+}
+
+fn timer_queue(ctxt: &Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream {
+ let tasks = &analysis.timer_queue.tasks;
+
+ if tasks.is_empty() {
+ return quote!();
+ }
+
+ let mut items = vec![];
+
+ let enum_ = &ctxt.schedule_enum;
+ items.push(quote!(
+ #[allow(dead_code)]
+ #[allow(non_camel_case_types)]
+ #[derive(Clone, Copy)]
+ enum #enum_ { #(#tasks,)* }
+ ));
+
+ let cap = mk_typenum_capacity(analysis.timer_queue.capacity, false);
+ let tq = &ctxt.timer_queue;
+ let symbol = format!("TIMER_QUEUE::{}", tq);
+ items.push(quote!(
+ #[export_name = #symbol]
+ static mut #tq:
+ rtfm::export::MaybeUninit<rtfm::export::TimerQueue<#enum_, #cap>> =
+ rtfm::export::MaybeUninit::uninitialized();
+ ));
+
+ items.push(mk_resource(
+ ctxt,
+ tq,
+ quote!(rtfm::export::TimerQueue<#enum_, #cap>),
+ analysis.timer_queue.ceiling,
+ quote!(#tq.get_mut()),
+ app,
+ None,
+ ));
+
+ let priority = &ctxt.priority;
+ let device = &app.args.device;
+ let arms = tasks
+ .iter()
+ .map(|task| {
+ let level = app.tasks[task].args.priority;
+ let tenum = &ctxt.enums[&level];
+ let ready = &ctxt.ready_queues[&level];
+ let dispatcher = &analysis.dispatchers[&level].interrupt;
+
+ quote!(
+ #enum_::#task => {
+ (#ready { #priority }).lock(|rq| {
+ rq.split().0.enqueue_unchecked((#tenum::#task, index))
+ });
+
+ rtfm::pend(#device::Interrupt::#dispatcher);
+ }
+ )
+ })
+ .collect::<Vec<_>>();
+
+ let logical_prio = analysis.timer_queue.priority;
+ items.push(quote!(
+ #[rtfm::export::exception]
+ #[doc(hidden)]
+ unsafe fn SysTick() {
+ use rtfm::Mutex;
+
+ let ref #priority = core::cell::Cell::new(#logical_prio);
+
+ rtfm::export::run(|| {
+ rtfm::export::sys_tick(#tq { #priority }, |task, index| {
+ match task {
+ #(#arms)*
+ }
+ });
+ })
+ }
+ ));
+
+ quote!(#(#items)*)
+}
+
+fn pre_init(ctxt: &Context, analysis: &Analysis) -> proc_macro2::TokenStream {
+ let mut exprs = vec![];
+
+ // FIXME(MaybeUninit) Because we are using a fake MaybeUninit we need to set the Option tag to
+ // Some; otherwise the get_ref and get_mut could result in UB. Also heapless collections can't
+ // be constructed in const context; we have to initialize them at runtime (i.e. here).
+
+ // these are `MaybeUninit` arrays
+ for inputs in ctxt.inputs.values() {
+ exprs.push(quote!(#inputs.set(core::mem::uninitialized());))
+ }
+
+ #[cfg(feature = "timer-queue")]
+ for inputs in ctxt.scheduleds.values() {
+ exprs.push(quote!(#inputs.set(core::mem::uninitialized());))
+ }
+
+ // these are `MaybeUninit` `ReadyQueue`s
+ for queue in ctxt.ready_queues.values() {
+ exprs.push(quote!(#queue.set(rtfm::export::ReadyQueue::new());))
+ }
+
+ // these are `MaybeUninit` `FreeQueue`s
+ for free in ctxt.free_queues.values() {
+ exprs.push(quote!(#free.set(rtfm::export::FreeQueue::new());))
+ }
+
+ // end-of-FIXME
+
+ // Initialize the timer queue
+ if !analysis.timer_queue.tasks.is_empty() {
+ let tq = &ctxt.timer_queue;
+ exprs.push(quote!(#tq.set(rtfm::export::TimerQueue::new(p.SYST));));
+ }
+
+ // Populate the `FreeQueue`s
+ for (task, alias) in &ctxt.free_queues {
+ let capacity = analysis.capacities[task];
+ exprs.push(quote!(
+ for i in 0..#capacity {
+ #alias.get_mut().enqueue_unchecked(i);
+ }
+ ))
+ }
+
+ // Set the cycle count to 0 and disable it while `init` executes
+ if cfg!(feature = "timer-queue") {
+ exprs.push(quote!(p.DWT.ctrl.modify(|r| r & !1);));
+ exprs.push(quote!(p.DWT.cyccnt.write(0);));
+ }
+
+ quote!(
+ let mut p = rtfm::export::Peripherals::steal();
+ #(#exprs)*
+ )
+}
+
+fn assertions(app: &App, analysis: &Analysis) -> proc_macro2::TokenStream {
+ let mut items = vec![];
+
+ for ty in &analysis.assert_sync {
+ items.push(quote!(rtfm::export::assert_sync::<#ty>()));
+ }
+
+ for task in &analysis.tasks_assert_send {
+ let ty = tuple_ty(&app.tasks[task].inputs);
+ items.push(quote!(rtfm::export::assert_send::<#ty>()));
+ }
+
+ // all late resources need to be `Send`
+ for ty in &analysis.resources_assert_send {
+ items.push(quote!(rtfm::export::assert_send::<#ty>()));
+ }
+
+ quote!(#(#items;)*)
+}
+
+fn mk_resource(
+ ctxt: &Context,
+ struct_: &Ident,
+ ty: proc_macro2::TokenStream,
+ ceiling: u8,
+ ptr: proc_macro2::TokenStream,
+ app: &App,
+ module: Option<&mut Vec<proc_macro2::TokenStream>>,
+) -> proc_macro2::TokenStream {
+ let priority = &ctxt.priority;
+ let device = &app.args.device;
+
+ let mut items = vec![];
+
+ let path = if let Some(module) = module {
+ let doc = format!("`{}`", ty);
+ module.push(quote!(
+ #[doc = #doc]
+ pub struct #struct_<'a> {
+ #[doc(hidden)]
+ pub #priority: &'a core::cell::Cell<u8>,
+ }
+ ));
+
+ quote!(resources::#struct_)
+ } else {
+ items.push(quote!(
+ struct #struct_<'a> {
+ #priority: &'a core::cell::Cell<u8>,
+ }
+ ));
+
+ quote!(#struct_)
+ };
+
+ items.push(quote!(
+ unsafe impl<'a> rtfm::Mutex for #path<'a> {
+ const CEILING: u8 = #ceiling;
+ const NVIC_PRIO_BITS: u8 = #device::NVIC_PRIO_BITS;
+ type Data = #ty;
+
+ #[inline(always)]
+ unsafe fn priority(&self) -> &core::cell::Cell<u8> {
+ &self.#priority
+ }
+
+ #[inline(always)]
+ fn ptr(&self) -> *mut Self::Data {
+ unsafe { #ptr }
+ }
+ }
+ ));
+
+ quote!(#(#items)*)
+}
+
+fn mk_capacity_literal(capacity: u8) -> LitInt {
+ LitInt::new(u64::from(capacity), IntSuffix::None, Span::call_site())
+}
+
+fn mk_typenum_capacity(capacity: u8, power_of_two: bool) -> proc_macro2::TokenStream {
+ let capacity = if power_of_two {
+ capacity
+ .checked_next_power_of_two()
+ .expect("capacity.next_power_of_two()")
+ } else {
+ capacity
+ };
+
+ let ident = Ident::new(&format!("U{}", capacity), Span::call_site());
+
+ quote!(rtfm::export::consts::#ident)
+}
+
+fn mk_ident() -> Ident {
+ static CALL_COUNT: AtomicUsize = AtomicUsize::new(0);
+
+ let elapsed = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
+
+ let secs = elapsed.as_secs();
+ let nanos = elapsed.subsec_nanos();
+
+ let count = CALL_COUNT.fetch_add(1, Ordering::SeqCst) as u32;
+ let mut seed: [u8; 16] = [0; 16];
+
+ for (i, v) in seed.iter_mut().take(8).enumerate() {
+ *v = ((secs >> (i * 8)) & 0xFF) as u8
+ }
+
+ for (i, v) in seed.iter_mut().skip(8).take(4).enumerate() {
+ *v = ((nanos >> (i * 8)) & 0xFF) as u8
+ }
+
+ for (i, v) in seed.iter_mut().skip(12).enumerate() {
+ *v = ((count >> (i * 8)) & 0xFF) as u8
+ }
+
+ let mut rng = rand::rngs::SmallRng::from_seed(seed);
+ Ident::new(
+ &(0..16)
+ .map(|i| {
+ if i == 0 || rng.gen() {
+ ('a' as u8 + rng.gen::<u8>() % 25) as char
+ } else {
+ ('0' as u8 + rng.gen::<u8>() % 10) as char
+ }
+ })
+ .collect::<String>(),
+ Span::call_site(),
+ )
+}
+
+// `once = true` means that these locals will be called from a function that will run *once*
+fn mk_locals(locals: &HashMap<Ident, Static>, once: bool) -> proc_macro2::TokenStream {
+ let lt = if once { Some(quote!('static)) } else { None };
+
+ let locals = locals
+ .iter()
+ .map(|(name, static_)| {
+ let attrs = &static_.attrs;
+ let expr = &static_.expr;
+ let ident = name;
+ let ty = &static_.ty;
+
+ quote!(
+ #[allow(non_snake_case)]
+ let #ident: &#lt mut #ty = {
+ #(#attrs)*
+ static mut #ident: #ty = #expr;
+
+ unsafe { &mut #ident }
+ };
+ )
+ })
+ .collect::<Vec<_>>();
+
+ quote!(#(#locals)*)
+}
+
+fn tuple_pat(inputs: &[ArgCaptured]) -> proc_macro2::TokenStream {
+ if inputs.len() == 1 {
+ let pat = &inputs[0].pat;
+ quote!(#pat)
+ } else {
+ let pats = inputs.iter().map(|i| &i.pat).collect::<Vec<_>>();
+
+ quote!(#(#pats,)*)
+ }
+}
+
+fn tuple_ty(inputs: &[ArgCaptured]) -> proc_macro2::TokenStream {
+ if inputs.len() == 1 {
+ let ty = &inputs[0].ty;
+ quote!(#ty)
+ } else {
+ let tys = inputs.iter().map(|i| &i.ty).collect::<Vec<_>>();
+
+ quote!((#(#tys,)*))
+ }
+}
+
+#[derive(Clone, Eq, Hash, PartialEq)]
+enum Kind {
+ Exception(Ident),
+ Idle,
+ Init,
+ Interrupt(Ident),
+ Task(Ident),
+}
+
+impl Kind {
+ fn ident(&self) -> Ident {
+ match self {
+ Kind::Init => Ident::new("init", Span::call_site()),
+ Kind::Idle => Ident::new("idle", Span::call_site()),
+ Kind::Task(name) | Kind::Interrupt(name) | Kind::Exception(name) => name.clone(),
+ }
+ }
+
+ fn is_idle(&self) -> bool {
+ *self == Kind::Idle
+ }
+
+ fn is_init(&self) -> bool {
+ *self == Kind::Init
+ }
+
+ fn runs_once(&self) -> bool {
+ match *self {
+ Kind::Init | Kind::Idle => true,
+ _ => false,
+ }
+ }
+}