diff options
Diffstat (limited to 'macros/src')
25 files changed, 2732 insertions, 4370 deletions
diff --git a/macros/src/analyze.rs b/macros/src/analyze.rs index cfd8ebc9..38018c8c 100644 --- a/macros/src/analyze.rs +++ b/macros/src/analyze.rs @@ -1,257 +1,48 @@ -use std::{ - cmp, - collections::{BTreeMap, HashMap, HashSet}, -}; - -use syn::{Attribute, Ident, Type}; - -use crate::syntax::{App, Idents}; +use core::ops; +use std::collections::{BTreeMap, BTreeSet}; -pub type Ownerships = HashMap<Ident, Ownership>; +use rtic_syntax::{ + analyze::{self, Priority}, + ast::App, + P, +}; +use syn::Ident; +/// Extend the upstream `Analysis` struct with our field pub struct Analysis { - /// Capacities of free queues - pub capacities: Capacities, - pub dispatchers: Dispatchers, - // Ceilings of free queues - pub free_queues: HashMap<Ident, u8>, - pub resources_assert_send: HashSet<Box<Type>>, - pub tasks_assert_send: HashSet<Ident>, - /// Types of RO resources that need to be Sync - pub assert_sync: HashSet<Box<Type>>, - // Resource ownership - pub ownerships: Ownerships, - // Ceilings of ready queues - pub ready_queues: HashMap<u8, u8>, - pub timer_queue: TimerQueue, -} - -#[derive(Clone, Copy, PartialEq)] -pub enum Ownership { - // NOTE priorities and ceilings are "logical" (0 = lowest priority, 255 = highest priority) - Owned { priority: u8 }, - CoOwned { priority: u8 }, - Shared { ceiling: u8 }, -} - -impl Ownership { - pub fn needs_lock(&self, priority: u8) -> bool { - match *self { - Ownership::Owned { .. } | Ownership::CoOwned { .. } => false, - Ownership::Shared { ceiling } => { - debug_assert!(ceiling >= priority); - - priority < ceiling - } - } - } - - pub fn is_owned(&self) -> bool { - match *self { - Ownership::Owned { .. } => true, - _ => false, - } - } -} - -pub struct Dispatcher { - /// Attributes to apply to the dispatcher - pub attrs: Vec<Attribute>, - pub interrupt: Ident, - /// Tasks dispatched at this priority level - pub tasks: Vec<Ident>, - // Queue capacity - pub capacity: u8, + parent: P<analyze::Analysis>, + pub interrupts: BTreeMap<Priority, Ident>, } -/// Priority -> Dispatcher -pub type Dispatchers = BTreeMap<u8, Dispatcher>; - -pub type Capacities = HashMap<Ident, u8>; - -pub fn app(app: &App) -> Analysis { - // Ceiling analysis of R/W resource and Sync analysis of RO resources - // (Resource shared by tasks that run at different priorities need to be `Sync`) - let mut ownerships = Ownerships::new(); - let mut resources_assert_send = HashSet::new(); - let mut tasks_assert_send = HashSet::new(); - let mut assert_sync = HashSet::new(); - - for (priority, res) in app.resource_accesses() { - if let Some(ownership) = ownerships.get_mut(res) { - match *ownership { - Ownership::Owned { priority: ceiling } - | Ownership::CoOwned { priority: ceiling } - | Ownership::Shared { ceiling } - if priority != ceiling => - { - *ownership = Ownership::Shared { - ceiling: cmp::max(ceiling, priority), - }; - - let res = &app.resources[res]; - if res.mutability.is_none() { - assert_sync.insert(res.ty.clone()); - } - } - Ownership::Owned { priority: ceiling } if ceiling == priority => { - *ownership = Ownership::CoOwned { priority }; - } - _ => {} - } - - continue; - } - - ownerships.insert(res.clone(), Ownership::Owned { priority }); - } - - // Compute sizes of free queues - // We assume at most one message per `spawn` / `schedule` - let mut capacities: Capacities = app.tasks.keys().map(|task| (task.clone(), 0)).collect(); - for (_, task) in app.spawn_calls().chain(app.schedule_calls()) { - *capacities.get_mut(task).expect("BUG: capacities.get_mut") += 1; - } - - // Override computed capacities if user specified a capacity in `#[task]` - for (name, task) in &app.tasks { - if let Some(cap) = task.args.capacity { - *capacities.get_mut(name).expect("BUG: capacities.get_mut") = cap; - } - } - - // Compute the size of the timer queue - // Compute the priority of the timer queue, which matches the priority of the highest - // `schedule`-able task - let mut tq_capacity = 0; - let mut tq_priority = 1; - let mut tq_tasks = Idents::new(); - for (_, task) in app.schedule_calls() { - tq_capacity += capacities[task]; - tq_priority = cmp::max(tq_priority, app.tasks[task].args.priority); - tq_tasks.insert(task.clone()); - } - - // Compute dispatchers capacities - // Determine which tasks are dispatched by which dispatcher - // Compute the timer queue priority which matches the priority of the highest priority - // dispatcher - let mut dispatchers = Dispatchers::new(); - let mut free_interrupts = app.free_interrupts.iter(); - let mut tasks = app.tasks.iter().collect::<Vec<_>>(); - tasks.sort_by(|l, r| l.1.args.priority.cmp(&r.1.args.priority)); - for (name, task) in tasks { - let dispatcher = dispatchers.entry(task.args.priority).or_insert_with(|| { - let (name, fi) = free_interrupts - .next() - .expect("BUG: not enough free_interrupts"); - - Dispatcher { - attrs: fi.attrs.clone(), - capacity: 0, - interrupt: name.clone(), - tasks: vec![], - } - }); - - dispatcher.capacity += capacities[name]; - dispatcher.tasks.push(name.clone()); - } - - // All messages sent from `init` need to be `Send` - for task in app.init.args.spawn.iter().chain(&app.init.args.schedule) { - tasks_assert_send.insert(task.clone()); - } - - // All late resources need to be `Send`, unless they are owned by `idle` - for (name, res) in &app.resources { - let owned_by_idle = Ownership::Owned { priority: 0 }; - if res.expr.is_none() - && ownerships - .get(name) - .map(|ship| *ship != owned_by_idle) - .unwrap_or(false) - { - resources_assert_send.insert(res.ty.clone()); - } - } - - // All resources shared with init need to be `Send`, unless they are owned by `idle` - // This is equivalent to late initialization (e.g. `static mut LATE: Option<T> = None`) - for name in &app.init.args.resources { - let owned_by_idle = Ownership::Owned { priority: 0 }; - if ownerships - .get(name) - .map(|ship| *ship != owned_by_idle) - .unwrap_or(false) - { - resources_assert_send.insert(app.resources[name].ty.clone()); - } - } - - // Ceiling analysis of free queues (consumer end point) -- first pass - // Ceiling analysis of ready queues (producer end point) - // Also compute more Send-ness requirements - let mut free_queues: HashMap<_, _> = app.tasks.keys().map(|task| (task.clone(), 0)).collect(); - let mut ready_queues: HashMap<_, _> = dispatchers.keys().map(|level| (*level, 0)).collect(); - for (priority, task) in app.spawn_calls() { - if let Some(priority) = priority { - // Users of `spawn` contend for the to-be-spawned task FREE_QUEUE - let c = free_queues.get_mut(task).expect("BUG: free_queue.get_mut"); - *c = cmp::max(*c, priority); - - let c = ready_queues - .get_mut(&app.tasks[task].args.priority) - .expect("BUG: ready_queues.get_mut"); - *c = cmp::max(*c, priority); - - // Send is required when sending messages from a task whose priority doesn't match the - // priority of the receiving task - if app.tasks[task].args.priority != priority { - tasks_assert_send.insert(task.clone()); - } - } else { - // spawns from `init` are excluded from the ceiling analysis - } - } - - // Ceiling analysis of free queues (consumer end point) -- second pass - // Ceiling analysis of the timer queue - let mut tq_ceiling = tq_priority; - for (priority, task) in app.schedule_calls() { - if let Some(priority) = priority { - // Users of `schedule` contend for the to-be-spawned task FREE_QUEUE (consumer end point) - let c = free_queues.get_mut(task).expect("BUG: free_queue.get_mut"); - *c = cmp::max(*c, priority); - - // Users of `schedule` contend for the timer queu - tq_ceiling = cmp::max(tq_ceiling, priority); - } else { - // spawns from `init` are excluded from the ceiling analysis - } - } +impl ops::Deref for Analysis { + type Target = analyze::Analysis; - Analysis { - capacities, - dispatchers, - free_queues, - tasks_assert_send, - resources_assert_send, - assert_sync, - ownerships, - ready_queues, - timer_queue: TimerQueue { - capacity: tq_capacity, - ceiling: tq_ceiling, - priority: tq_priority, - tasks: tq_tasks, - }, + fn deref(&self) -> &Self::Target { + &self.parent } } -pub struct TimerQueue { - pub capacity: u8, - pub ceiling: u8, - pub priority: u8, - pub tasks: Idents, +// Assign an `extern` interrupt to each priority level +pub fn app(analysis: P<analyze::Analysis>, app: &App) -> P<Analysis> { + let mut interrupts = BTreeMap::new(); + let priorities = app + .software_tasks + .values() + .filter_map(|task| Some(task.args.priority)) + .chain(analysis.timer_queues.first().map(|tq| tq.priority)) + .collect::<BTreeSet<_>>(); + + if !priorities.is_empty() { + interrupts = priorities + .iter() + .cloned() + .rev() + .zip(app.extern_interrupts.keys().cloned()) + .collect(); + } + + P::new(Analysis { + parent: analysis, + interrupts, + }) } diff --git a/macros/src/check.rs b/macros/src/check.rs index 4adc2c17..0e57bb73 100644 --- a/macros/src/check.rs +++ b/macros/src/check.rs @@ -1,374 +1,163 @@ -use std::{collections::HashSet, iter}; +use std::collections::HashSet; use proc_macro2::Span; -use syn::{parse, spanned::Spanned, Block, Expr, Stmt}; - -use crate::syntax::App; +use rtic_syntax::{ + analyze::Analysis, + ast::{App, CustomArg}, +}; +use syn::{parse, Path}; + +pub struct Extra<'a> { + pub device: &'a Path, + pub monotonic: Option<&'a Path>, + pub peripherals: bool, +} -pub fn app(app: &App) -> parse::Result<()> { - // Check that all referenced resources have been declared - for res in app - .idle - .as_ref() - .map(|idle| -> Box<dyn Iterator<Item = _>> { Box::new(idle.args.resources.iter()) }) - .unwrap_or_else(|| Box::new(iter::empty())) - .chain(&app.init.args.resources) - .chain(app.exceptions.values().flat_map(|e| &e.args.resources)) - .chain(app.interrupts.values().flat_map(|i| &i.args.resources)) - .chain(app.tasks.values().flat_map(|t| &t.args.resources)) - { - if !app.resources.contains_key(res) { - return Err(parse::Error::new( - res.span(), - "this resource has NOT been declared", - )); - } +impl<'a> Extra<'a> { + pub fn monotonic(&self) -> &'a Path { + self.monotonic.expect("UNREACHABLE") } +} - // Check that late resources have not been assigned to `init` - for res in &app.init.args.resources { - if app.resources.get(res).unwrap().expr.is_none() { - return Err(parse::Error::new( - res.span(), - "late resources can NOT be assigned to `init`", - )); - } - } +pub fn app<'a>(app: &'a App, analysis: &Analysis) -> parse::Result<Extra<'a>> { + // Check that all exceptions are valid; only exceptions with configurable priorities are + // accepted + for (name, task) in &app.hardware_tasks { + let name_s = task.args.binds.to_string(); + match &*name_s { + "SysTick" => { + // If the timer queue is used, then SysTick is unavailable + if !analysis.timer_queues.is_empty() { + return Err(parse::Error::new( + name.span(), + "this exception can't be used because it's being used by the runtime", + )); + } else { + // OK + } + } - // Check that all late resources have been initialized in `#[init]` if `init` has signature - // `fn()` - if !app.init.returns_late_resources { - for res in - app.resources - .iter() - .filter_map(|(name, res)| if res.expr.is_none() { Some(name) } else { None }) - { - if app.init.assigns.iter().all(|assign| assign.left != *res) { + "NonMaskableInt" | "HardFault" => { return Err(parse::Error::new( - res.span(), - "late resources MUST be initialized at the end of `init`", + name.span(), + "only exceptions with configurable priority can be used as hardware tasks", )); } - } - } - // Check that all referenced tasks have been declared - for task in app - .idle - .as_ref() - .map(|idle| -> Box<dyn Iterator<Item = _>> { - Box::new(idle.args.schedule.iter().chain(&idle.args.spawn)) - }) - .unwrap_or_else(|| Box::new(iter::empty())) - .chain(&app.init.args.schedule) - .chain(&app.init.args.spawn) - .chain( - app.exceptions - .values() - .flat_map(|e| e.args.schedule.iter().chain(&e.args.spawn)), - ) - .chain( - app.interrupts - .values() - .flat_map(|i| i.args.schedule.iter().chain(&i.args.spawn)), - ) - .chain( - app.tasks - .values() - .flat_map(|t| t.args.schedule.iter().chain(&t.args.spawn)), - ) - { - if !app.tasks.contains_key(task) { - return Err(parse::Error::new( - task.span(), - "this task has NOT been declared", - )); + _ => {} } } - // Check that there are enough free interrupts to dispatch all tasks - let ndispatchers = app - .tasks - .values() - .map(|t| t.args.priority) - .collect::<HashSet<_>>() - .len(); - if ndispatchers > app.free_interrupts.len() { - return Err(parse::Error::new( - Span::call_site(), - &*format!( - "{} free interrupt{} (`extern {{ .. }}`) {} required to dispatch all soft tasks", - ndispatchers, - if ndispatchers > 1 { "s" } else { "" }, - if ndispatchers > 1 { "are" } else { "is" }, - ), - )); - } - - // Check that free interrupts are not being used - for (handler, interrupt) in &app.interrupts { - let name = interrupt.args.binds(handler); + // Check that external (device-specific) interrupts are not named after known (Cortex-M) + // exceptions + for name in app.extern_interrupts.keys() { + let name_s = name.to_string(); - if app.free_interrupts.contains_key(name) { - return Err(parse::Error::new( - name.span(), - "free interrupts (`extern { .. }`) can't be used as interrupt handlers", - )); - } - } - - // Check that `init` contains no early returns *if* late resources exist and `init` signature is - // `fn()` - if app.resources.values().any(|res| res.expr.is_none()) { - if !app.init.returns_late_resources { - for stmt in &app.init.stmts { - noreturn_stmt(stmt)?; + match &*name_s { + "NonMaskableInt" | "HardFault" | "MemoryManagement" | "BusFault" | "UsageFault" + | "SecureFault" | "SVCall" | "DebugMonitor" | "PendSV" | "SysTick" => { + return Err(parse::Error::new( + name.span(), + "Cortex-M exceptions can't be used as `extern` interrupts", + )); } - } - } else if app.init.returns_late_resources { - return Err(parse::Error::new( - Span::call_site(), - "`init` signature must be `[unsafe] fn()` if there are no late resources", - )); - } - - Ok(()) -} - -// checks that the given block contains no instance of `return` -fn noreturn_block(block: &Block) -> Result<(), parse::Error> { - for stmt in &block.stmts { - noreturn_stmt(stmt)?; - } - - Ok(()) -} -// checks that the given statement contains no instance of `return` -fn noreturn_stmt(stmt: &Stmt) -> Result<(), parse::Error> { - match stmt { - // `let x = ..` -- this may contain a return in the RHS - Stmt::Local(local) => { - if let Some(ref init) = local.init { - noreturn_expr(&init.1)? - } + _ => {} } - - // items have no effect on control flow - Stmt::Item(..) => {} - - Stmt::Expr(expr) => noreturn_expr(expr)?, - - Stmt::Semi(expr, ..) => noreturn_expr(expr)?, } - Ok(()) -} - -// checks that the given expression contains no `return` -fn noreturn_expr(expr: &Expr) -> Result<(), parse::Error> { - match expr { - Expr::Box(b) => noreturn_expr(&b.expr)?, - - Expr::InPlace(ip) => { - noreturn_expr(&ip.place)?; - noreturn_expr(&ip.value)?; - } - - Expr::Array(a) => { - for elem in &a.elems { - noreturn_expr(elem)?; - } - } - - Expr::Call(c) => { - noreturn_expr(&c.func)?; - - for arg in &c.args { - noreturn_expr(arg)?; - } - } - - Expr::MethodCall(mc) => { - noreturn_expr(&mc.receiver)?; - - for arg in &mc.args { - noreturn_expr(arg)?; - } - } - - Expr::Tuple(t) => { - for elem in &t.elems { - noreturn_expr(elem)?; - } - } - - Expr::Binary(b) => { - noreturn_expr(&b.left)?; - noreturn_expr(&b.right)?; - } - - Expr::Unary(u) => { - noreturn_expr(&u.expr)?; - } - - Expr::Lit(..) => {} - - Expr::Cast(c) => { - noreturn_expr(&c.expr)?; - } - - Expr::Type(t) => { - noreturn_expr(&t.expr)?; - } - - Expr::Let(l) => { - noreturn_expr(&l.expr)?; - } - - Expr::If(i) => { - noreturn_expr(&i.cond)?; + // Check that there are enough external interrupts to dispatch the software tasks and the timer + // queue handler + let mut first = None; + let priorities = app + .software_tasks + .iter() + .filter_map(|(name, task)| { + first = Some(name); + Some(task.args.priority) + }) + .chain(analysis.timer_queues.first().map(|tq| tq.priority)) + .collect::<HashSet<_>>(); + + let need = priorities.len(); + let given = app.extern_interrupts.len(); + if need > given { + let s = { + format!( + "not enough `extern` interrupts to dispatch \ + all software tasks (need: {}; given: {})", + need, given + ) + }; + + // If not enough tasks and first still is None, may cause + // "custom attribute panicked" due to unwrap on None + return Err(parse::Error::new(first.unwrap().span(), &s)); + } - noreturn_block(&i.then_branch)?; + let mut device = None; + let mut monotonic = None; + let mut peripherals = false; - if let Some(ref e) = i.else_branch { - noreturn_expr(&e.1)?; - } - } + for (k, v) in &app.args.custom { + let ks = k.to_string(); - Expr::While(w) => { - noreturn_expr(&w.cond)?; - noreturn_block(&w.body)?; - } - - Expr::ForLoop(fl) => { - noreturn_expr(&fl.expr)?; - noreturn_block(&fl.body)?; - } + match &*ks { + "device" => match v { + CustomArg::Path(p) => device = Some(p), - Expr::Loop(l) => { - noreturn_block(&l.body)?; - } - - Expr::Match(m) => { - noreturn_expr(&m.expr)?; - - for arm in &m.arms { - if let Some(g) = &arm.guard { - noreturn_expr(&g.1)?; + _ => { + return Err(parse::Error::new( + k.span(), + "unexpected argument value; this should be a path", + )); } + }, - noreturn_expr(&arm.body)?; - } - } - - // we don't care about `return`s inside closures - Expr::Closure(..) => {} - - Expr::Unsafe(u) => { - noreturn_block(&u.block)?; - } + "monotonic" => match v { + CustomArg::Path(p) => monotonic = Some(p), - Expr::Block(b) => { - noreturn_block(&b.block)?; - } - - Expr::Assign(a) => { - noreturn_expr(&a.left)?; - noreturn_expr(&a.right)?; - } - - Expr::AssignOp(ao) => { - noreturn_expr(&ao.left)?; - noreturn_expr(&ao.right)?; - } - - Expr::Field(f) => { - noreturn_expr(&f.base)?; - } - - Expr::Index(i) => { - noreturn_expr(&i.expr)?; - noreturn_expr(&i.index)?; - } - - Expr::Range(r) => { - if let Some(ref f) = r.from { - noreturn_expr(f)?; - } - - if let Some(ref t) = r.to { - noreturn_expr(t)?; - } - } - - Expr::Path(..) => {} - - Expr::Reference(r) => { - noreturn_expr(&r.expr)?; - } - - Expr::Break(b) => { - if let Some(ref e) = b.expr { - noreturn_expr(e)?; - } - } - - Expr::Continue(..) => {} - - Expr::Return(r) => { - return Err(parse::Error::new( - r.span(), - "`init` is *not* allowed to early return", - )); - } - - // we can not analyze this - Expr::Macro(..) => {} - - Expr::Struct(s) => { - for field in &s.fields { - noreturn_expr(&field.expr)?; - } - - if let Some(ref rest) = s.rest { - noreturn_expr(rest)?; - } - } - - Expr::Repeat(r) => { - noreturn_expr(&r.expr)?; - noreturn_expr(&r.len)?; - } - - Expr::Paren(p) => { - noreturn_expr(&p.expr)?; - } - - Expr::Group(g) => { - noreturn_expr(&g.expr)?; - } - - Expr::Try(t) => { - noreturn_expr(&t.expr)?; - } - - // we don't care about `return`s inside async blocks - Expr::Async(..) => {} - - Expr::TryBlock(tb) => { - noreturn_block(&tb.block)?; - } + _ => { + return Err(parse::Error::new( + k.span(), + "unexpected argument value; this should be a path", + )); + } + }, + + "peripherals" => match v { + CustomArg::Bool(x) => peripherals = if *x { true } else { false }, + _ => { + return Err(parse::Error::new( + k.span(), + "unexpected argument value; this should be a boolean", + )); + } + }, - Expr::Yield(y) => { - if let Some(expr) = &y.expr { - noreturn_expr(expr)?; + _ => { + return Err(parse::Error::new(k.span(), "unexpected argument")); } } + } - // we can not analyze this - Expr::Verbatim(..) => {} + if !&analysis.timer_queues.is_empty() && monotonic.is_none() { + return Err(parse::Error::new( + Span::call_site(), + "a `monotonic` timer must be specified to use the `schedule` API", + )); } - Ok(()) + if let Some(device) = device { + Ok(Extra { + device, + monotonic, + peripherals, + }) + } else { + Err(parse::Error::new( + Span::call_site(), + "a `device` argument must be specified in `#[rtic::app]`", + )) + } } diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs index 1b3f67b8..f230d395 100644 --- a/macros/src/codegen.rs +++ b/macros/src/codegen.rs @@ -1,2267 +1,182 @@ -#![deny(warnings)] - -use proc_macro::TokenStream; -use std::{ - collections::{BTreeMap, HashMap}, - time::{SystemTime, UNIX_EPOCH}, -}; - -use proc_macro2::Span; +use proc_macro2::TokenStream as TokenStream2; use quote::quote; -use rand::{Rng, SeedableRng}; -use syn::{parse_quote, ArgCaptured, Attribute, Ident, IntSuffix, LitInt}; - -use crate::{ - analyze::{Analysis, Ownership}, - 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 = BTreeMap<Ident, Ident>; - -struct Context { - // Alias - #[cfg(feature = "timer-queue")] - baseline: Ident, - dispatchers: BTreeMap<u8, Dispatcher>, - // Alias (`fn`) - idle: Ident, - // Alias (`fn`) - init: Ident, - // Alias - priority: Ident, - // For non-singletons this maps the resource name to its `static mut` variable name - statics: Aliases, - /// Task -> Alias (`struct`) - resources: HashMap<Kind, Resources>, - // Alias (`enum`) - schedule_enum: Ident, - // Task -> Alias (`fn`) - schedule_fn: Aliases, - tasks: BTreeMap<Ident, Task>, - // Alias (`struct` / `static mut`) - timer_queue: Ident, - // Generator of Ident names or suffixes - ident_gen: IdentGenerator, -} - -struct Dispatcher { - enum_: Ident, - ready_queue: Ident, -} - -struct Task { - alias: Ident, - free_queue: Ident, - inputs: Ident, - spawn_fn: Ident, - - #[cfg(feature = "timer-queue")] - scheduleds: Ident, -} - -impl Default for Context { - fn default() -> Self { - let mut ident_gen = IdentGenerator::new(); - - Context { - #[cfg(feature = "timer-queue")] - baseline: ident_gen.mk_ident(None, false), - dispatchers: BTreeMap::new(), - idle: ident_gen.mk_ident(Some("idle"), false), - init: ident_gen.mk_ident(Some("init"), false), - priority: ident_gen.mk_ident(None, false), - statics: Aliases::new(), - resources: HashMap::new(), - schedule_enum: ident_gen.mk_ident(None, false), - schedule_fn: Aliases::new(), - tasks: BTreeMap::new(), - timer_queue: ident_gen.mk_ident(None, false), - ident_gen, - } - } -} - -struct Resources { - alias: Ident, - decl: proc_macro2::TokenStream, -} - -pub fn app(app: &App, analysis: &Analysis) -> TokenStream { - let mut ctxt = Context::default(); - - 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, has_late_resources) = 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 init = &ctxt.init; - let init_phase = if has_late_resources { - let assigns = app - .resources - .iter() - .filter_map(|(name, res)| { - if res.expr.is_none() { - let alias = &ctxt.statics[name]; - - Some(quote!(#alias.write(res.#name);)) - } else { - None - } - }) - .collect::<Vec<_>>(); - - quote!( - let res = #init(#init_arg); - #(#assigns)* - ) - } else { - quote!(#init(#init_arg);) - }; - - 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(&mut ctxt, app, analysis); - - let pre_init = pre_init(&ctxt, &app, analysis); - - let assertions = assertions(app, analysis); - - let main = ctxt.ident_gen.mk_ident(None, false); - quote!( - #resources - - #spawn - - #timer_queue - - #schedule - - #dispatchers_data - - #(#exceptions)* - - #root_interrupts - - const APP: () = { - #scoped_interrupts - - #(#dispatchers)* - }; - - #(#tasks)* - - #init_fn - - #idle_fn - - #[export_name = "main"] - #[allow(unsafe_code)] - #[doc(hidden)] - unsafe fn #main() -> ! { - #assertions - - rtfm::export::interrupt::disable(); - - #pre_init - - #init_phase - - #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 cfgs = &res.cfgs; - 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 = ctxt.ident_gen.mk_ident(None, true); // XXX is randomness required? - if let Some(Ownership::Shared { ceiling }) = analysis.ownerships.get(name) { - items.push(mk_resource( - ctxt, - cfgs, - 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 = ctxt.ident_gen.mk_ident(None, false); - let symbol = format!("{}::{}", name, alias); - - items.push( - expr.as_ref() - .map(|expr| { - quote!( - #(#attrs)* - #(#cfgs)* - #[doc = #symbol] - static mut #alias: #ty = #expr; - ) - }) - .unwrap_or_else(|| { - quote!( - #(#attrs)* - #(#cfgs)* - #[doc = #symbol] - static mut #alias: rtfm::export::MaybeUninit<#ty> = - rtfm::export::MaybeUninit::uninit(); - ) - }), - ); - - if let Some(Ownership::Shared { ceiling }) = analysis.ownerships.get(name) { - if res.mutability.is_some() { - let ptr = if res.expr.is_none() { - quote!(unsafe { &mut *#alias.as_mut_ptr() }) - } else { - quote!(unsafe { &mut #alias }) - }; - - items.push(mk_resource( - ctxt, - cfgs, - 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, bool) { - let attrs = &app.init.attrs; - let locals = mk_locals(&app.init.statics, true); - let stmts = &app.init.stmts; - // TODO remove in v0.5.x - let assigns = app - .init - .assigns - .iter() - .map(|assign| { - let attrs = &assign.attrs; - 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!( - #(#attrs)* - unsafe { #alias.write(#expr); } - ) - } else { - let left = &assign.left; - let right = &assign.right; - quote!( - #(#attrs)* - #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 (late_resources, late_resources_ident, ret) = if app.init.returns_late_resources { - // create `LateResources` struct in the root of the crate - let ident = ctxt.ident_gen.mk_ident(None, false); - - let fields = app - .resources - .iter() - .filter_map(|(name, res)| { - if res.expr.is_none() { - let ty = &res.ty; - Some(quote!(pub #name: #ty)) - } else { - None - } - }) - .collect::<Vec<_>>(); - - let late_resources = quote!( - #[allow(non_snake_case)] - pub struct #ident { - #(#fields),* - } - ); - - ( - Some(late_resources), - Some(ident), - Some(quote!(-> init::LateResources)), - ) - } else { - (None, None, None) - }; - let has_late_resources = late_resources.is_some(); - - let module = module( - ctxt, - Kind::Init, - !app.init.args.schedule.is_empty(), - !app.init.args.spawn.is_empty(), - app, - late_resources_ident, - ); - - #[cfg(feature = "timer-queue")] - let baseline = &ctxt.baseline; - let baseline_let = match () { - #[cfg(feature = "timer-queue")] - () => quote!(let ref #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; - ( - quote!( - #late_resources - - #module - - // unsafe trampoline to deter end-users from calling this non-reentrant function - #(#attrs)* - unsafe fn #init(core: rtfm::Peripherals) #ret { - #[inline(always)] - #unsafety fn init(mut core: rtfm::Peripherals) #ret { - #(#locals)* - - #baseline_let - - #prelude - - let mut device = unsafe { #device::Peripherals::steal() }; - - #start_let - - #(#stmts)* - - #(#assigns)* - } - - init(core) - } - ), - has_late_resources, - ) -} - -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 (handler, exception) in &app.exceptions { - let name = exception.args.binds(handler); - 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), - ))); - } - - 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.as_mut_ptr()).syst.set_clock_source(rtfm::export::SystClkSource::Core)), - ); - exprs.push(quote!((*#tq.as_mut_ptr()).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, - late_resources: Option<Ident>, -) -> 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 rtfm::export::Priority, - } - )); - } - - 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 rtfm::export::Priority, - } - )); - } else { - let baseline_field = match () { - #[cfg(feature = "timer-queue")] - () => { - let baseline = &ctxt.baseline; - quote!( - // NOTE this field is visible so we use a shared reference to make it - // immutable - #[doc(hidden)] - pub #baseline: &'a 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 rtfm::export::Priority, - } - )); - } - } - - 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", - }; - - if let Some(late_resources) = late_resources { - items.push(quote!( - pub use super::#late_resources as LateResources; - )); - } - - quote!( - #root - - #[doc = #doc] - #[allow(non_snake_case)] - 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)] pub #priority: &'a rtfm::export::Priority)); - exprs.push(parse_quote!(#priority)); - - let mut may_call_lock = false; - let mut needs_unsafe = false; - for name in resources { - let res = &app.resources[name]; - let cfgs = &res.cfgs; - - 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!( - #(#cfgs)* - pub #name: #name - )); - exprs.push(quote!( - #(#cfgs)* - #name: <#name as owned_singleton::Singleton>::new() - )); - continue; - } else { - defs.push(quote!( - #(#cfgs)* - pub #name: &'static #mut_ #ty - )); - } - } else { - // owned by someone else - if singleton { - needs_unsafe = true; - defs.push(quote!( - #(#cfgs)* - pub #name: &'a mut #name - )); - exprs.push(quote!( - #(#cfgs)* - #name: &mut <#name as owned_singleton::Singleton>::new() - )); - continue; - } else { - force_mut = true; - defs.push(quote!( - #(#cfgs)* - 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!( - #(#cfgs)* - #name: &mut #alias - )); - } else { - exprs.push(quote!( - #(#cfgs)* - #name: &#mut_ #alias - )); - } - } else { - let ownership = &analysis.ownerships[name]; - let mut exclusive = false; - - if ownership.needs_lock(logical_prio) { - may_call_lock = true; - if singleton { - if mut_.is_none() { - needs_unsafe = true; - defs.push(quote!( - #(#cfgs)* - pub #name: &'a #name - )); - exprs.push(quote!( - #(#cfgs)* - #name: &<#name as owned_singleton::Singleton>::new() - )); - continue; - } else { - // Generate a resource proxy - defs.push(quote!( - #(#cfgs)* - pub #name: resources::#name<'a> - )); - exprs.push(quote!( - #(#cfgs)* - #name: resources::#name { #priority } - )); - continue; - } - } else { - if mut_.is_none() { - defs.push(quote!( - #(#cfgs)* - pub #name: &'a #ty - )); - } else { - // Generate a resource proxy - defs.push(quote!( - #(#cfgs)* - pub #name: resources::#name<'a> - )); - exprs.push(quote!( - #(#cfgs)* - #name: resources::#name { #priority } - )); - continue; - } - } - } else { - if singleton { - if kind.runs_once() { - needs_unsafe = true; - defs.push(quote!( - #(#cfgs)* - pub #name: #name - )); - exprs.push(quote!( - #(#cfgs)* - #name: <#name as owned_singleton::Singleton>::new() - )); - } else { - needs_unsafe = true; - if ownership.is_owned() || mut_.is_none() { - defs.push(quote!( - #(#cfgs)* - pub #name: &'a #mut_ #name - )); - // XXX is randomness required? - let alias = ctxt.ident_gen.mk_ident(None, true); - items.push(quote!( - #(#cfgs)* - let #mut_ #alias = unsafe { - <#name as owned_singleton::Singleton>::new() - }; - )); - exprs.push(quote!( - #(#cfgs)* - #name: &#mut_ #alias - )); - } else { - may_call_lock = true; - defs.push(quote!( - #(#cfgs)* - pub #name: rtfm::Exclusive<'a, #name> - )); - // XXX is randomness required? - let alias = ctxt.ident_gen.mk_ident(None, true); - items.push(quote!( - #(#cfgs)* - let #mut_ #alias = unsafe { - <#name as owned_singleton::Singleton>::new() - }; - )); - exprs.push(quote!( - #(#cfgs)* - #name: rtfm::Exclusive(&mut #alias) - )); - } - } - continue; - } else { - if ownership.is_owned() || mut_.is_none() { - defs.push(quote!( - #(#cfgs)* - pub #name: &#lt #mut_ #ty - )); - } else { - exclusive = true; - may_call_lock = true; - defs.push(quote!( - #(#cfgs)* - pub #name: rtfm::Exclusive<#lt, #ty> - )); - } - } - } - - let alias = &ctxt.statics[name]; - needs_unsafe = true; - if initialized { - if exclusive { - exprs.push(quote!( - #(#cfgs)* - #name: rtfm::Exclusive(&mut #alias) - )); - } else { - exprs.push(quote!( - #(#cfgs)* - #name: &#mut_ #alias - )); - } - } else { - let expr = if mut_.is_some() { - quote!(&mut *#alias.as_mut_ptr()) - } else { - quote!(&*#alias.as_ptr()) - }; - - if exclusive { - exprs.push(quote!( - #(#cfgs)* - #name: rtfm::Exclusive(#expr) - )); - } else { - exprs.push(quote!( - #(#cfgs)* - #name: #expr - )); - } - } - } - } - - let alias = ctxt.ident_gen.mk_ident(None, false); - let unsafety = if needs_unsafe { - Some(quote!(unsafe)) - } else { - None - }; - - let defs = &defs; - 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() { - 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(), ctxt.ident_gen.mk_ident(None, false)); - } - - 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 = unsafe { rtfm::export::Priority::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, - None, - ); - - let unsafety = &idle.unsafety; - let idle = &ctxt.idle; - - ( - quote!( - #module - - // unsafe trampoline to deter end-users from calling this non-reentrant function - #(#attrs)* - unsafe fn #idle() -> ! { - #[inline(always)] - #unsafety fn idle() -> ! { - #(#locals)* - - #prelude - - #(#stmts)* - } - - idle() - } - ), - 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 stmts = &exception.stmts; - - let kind = Kind::Exception(ident.clone()); - let prelude = prelude( - ctxt, - kind.clone(), - &exception.args.resources, - &exception.args.spawn, - &exception.args.schedule, - app, - exception.args.priority, - analysis, - ); - - let module = module( - ctxt, - kind, - !exception.args.schedule.is_empty(), - !exception.args.spawn.is_empty(), - app, - None, - ); - - #[cfg(feature = "timer-queue")] - let baseline = &ctxt.baseline; - let baseline_let = match () { - #[cfg(feature = "timer-queue")] - () => quote!(let ref #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 locals = mk_locals(&exception.statics, false); - let symbol = exception.args.binds(ident).to_string(); - let alias = ctxt.ident_gen.mk_ident(None, false); - let unsafety = &exception.unsafety; - quote!( - #module - - // unsafe trampoline to deter end-users from calling this non-reentrant function - #[export_name = #symbol] - #(#attrs)* - unsafe fn #alias() { - #[inline(always)] - #unsafety fn exception() { - #(#locals)* - - #baseline_let - - #prelude - - #start_let - - rtfm::export::run(move || { - #(#stmts)* - }) - } - - exception() - } - ) - }) - .collect() -} - -fn interrupts( - ctxt: &mut Context, - app: &App, - analysis: &Analysis, -) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { +use rtic_syntax::ast::App; + +use crate::{analyze::Analysis, check::Extra}; + +mod assertions; +mod dispatchers; +mod hardware_tasks; +mod idle; +mod init; +mod locals; +mod module; +mod post_init; +mod pre_init; +mod resources; +mod resources_struct; +mod schedule; +mod schedule_body; +mod software_tasks; +mod spawn; +mod spawn_body; +mod timer_queue; +mod util; + +// TODO document the syntax here or in `rtic-syntax` +pub fn app(app: &App, analysis: &Analysis, extra: &Extra) -> TokenStream2 { + let mut mod_app = vec![]; + let mut mod_app_imports = vec![]; + let mut mains = vec![]; let mut root = vec![]; - let mut scoped = vec![]; - - for (ident, interrupt) in &app.interrupts { - let attrs = &interrupt.attrs; - let stmts = &interrupt.stmts; - - let kind = Kind::Interrupt(ident.clone()); - let prelude = prelude( - ctxt, - kind.clone(), - &interrupt.args.resources, - &interrupt.args.spawn, - &interrupt.args.schedule, - app, - interrupt.args.priority, - analysis, - ); - - root.push(module( - ctxt, - kind, - !interrupt.args.schedule.is_empty(), - !interrupt.args.spawn.is_empty(), - app, - None, - )); - - #[cfg(feature = "timer-queue")] - let baseline = &ctxt.baseline; - let baseline_let = match () { - #[cfg(feature = "timer-queue")] - () => quote!(let ref #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 locals = mk_locals(&interrupt.statics, false); - let alias = ctxt.ident_gen.mk_ident(None, false); - let symbol = interrupt.args.binds(ident).to_string(); - let unsafety = &interrupt.unsafety; - scoped.push(quote!( - // unsafe trampoline to deter end-users from calling this non-reentrant function - #(#attrs)* - #[export_name = #symbol] - unsafe fn #alias() { - #[inline(always)] - #unsafety fn interrupt() { - #(#locals)* - - #baseline_let - - #prelude - - #start_let - - rtfm::export::run(move || { - #(#stmts)* - }) - } - - interrupt() - } - )); - } - - (quote!(#(#root)*), quote!(#(#scoped)*)) -} - -fn tasks(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream { - let mut items = vec![]; - - // first pass to generate buffers (statics and resources) and spawn aliases - for (name, task) in &app.tasks { - #[cfg(feature = "timer-queue")] - let scheduleds_alias = ctxt.ident_gen.mk_ident(None, false); - let free_alias = ctxt.ident_gen.mk_ident(None, false); - let inputs_alias = ctxt.ident_gen.mk_ident(None, false); - let task_alias = ctxt.ident_gen.mk_ident(Some(&name.to_string()), false); - - let inputs = &task.inputs; - - let ty = tuple_ty(inputs); - - let capacity = analysis.capacities[name]; - let capacity_lit = mk_capacity_literal(capacity); - let capacity_ty = mk_typenum_capacity(capacity, true); - - let resource = mk_resource( - ctxt, - &[], - &free_alias, - quote!(rtfm::export::FreeQueue<#capacity_ty>), - *analysis.free_queues.get(name).unwrap_or(&0), - if cfg!(feature = "nightly") { - quote!(&mut #free_alias) - } else { - quote!(#free_alias.get_mut()) - }, - app, - None, - ); - - let scheduleds_static = match () { - #[cfg(feature = "timer-queue")] - () => { - let scheduleds_symbol = format!("{}::SCHEDULED_TIMES::{}", name, scheduleds_alias); - - if cfg!(feature = "nightly") { - let inits = - (0..capacity).map(|_| quote!(rtfm::export::MaybeUninit::uninit())); - - quote!( - #[doc = #scheduleds_symbol] - static mut #scheduleds_alias: - [rtfm::export::MaybeUninit<rtfm::Instant>; #capacity_lit] = - [#(#inits),*]; - ) - } else { - quote!( - #[doc = #scheduleds_symbol] - static mut #scheduleds_alias: - rtfm::export::MaybeUninit<[rtfm::Instant; #capacity_lit]> = - rtfm::export::MaybeUninit::uninit(); - ) - } - } - #[cfg(not(feature = "timer-queue"))] - () => quote!(), - }; - - let inputs_symbol = format!("{}::INPUTS::{}", name, inputs_alias); - let free_symbol = format!("{}::FREE_QUEUE::{}", name, free_alias); - if cfg!(feature = "nightly") { - let inits = (0..capacity).map(|_| quote!(rtfm::export::MaybeUninit::uninit())); - - items.push(quote!( - #[doc = #free_symbol] - static mut #free_alias: rtfm::export::FreeQueue<#capacity_ty> = unsafe { - rtfm::export::FreeQueue::new_sc() - }; - - #[doc = #inputs_symbol] - static mut #inputs_alias: [rtfm::export::MaybeUninit<#ty>; #capacity_lit] = - [#(#inits),*]; - )); - } else { - items.push(quote!( - #[doc = #free_symbol] - static mut #free_alias: rtfm::export::MaybeUninit< - rtfm::export::FreeQueue<#capacity_ty> - > = rtfm::export::MaybeUninit::uninit(); - - #[doc = #inputs_symbol] - static mut #inputs_alias: rtfm::export::MaybeUninit<[#ty; #capacity_lit]> = - rtfm::export::MaybeUninit::uninit(); - - )); - } - - items.push(quote!( - #resource - - #scheduleds_static - )); - - ctxt.tasks.insert( - name.clone(), - Task { - alias: task_alias, - free_queue: free_alias, - inputs: inputs_alias, - spawn_fn: ctxt.ident_gen.mk_ident(None, false), - - #[cfg(feature = "timer-queue")] - scheduleds: scheduleds_alias, - }, - ); - } + let mut user = vec![]; + let mut imports = vec![]; - // second pass to generate the actual task function - for (name, task) in &app.tasks { - let inputs = &task.inputs; - let locals = mk_locals(&task.statics, false); - let stmts = &task.stmts; - let unsafety = &task.unsafety; + // Generate the `main` function + let assertion_stmts = assertions::codegen(analysis); - let scheduled_let = match () { - #[cfg(feature = "timer-queue")] - () => { - let baseline = &ctxt.baseline; - quote!(let scheduled = *#baseline;) - } - #[cfg(not(feature = "timer-queue"))] - () => quote!(), - }; + let pre_init_stmts = pre_init::codegen(&app, analysis, extra); - let prelude = prelude( - ctxt, - Kind::Task(name.clone()), - &task.args.resources, - &task.args.spawn, - &task.args.schedule, - app, - task.args.priority, - analysis, - ); + let (mod_app_init, root_init, user_init, user_init_imports, call_init) = + init::codegen(app, analysis, extra); - items.push(module( - ctxt, - Kind::Task(name.clone()), - !task.args.schedule.is_empty(), - !task.args.spawn.is_empty(), - app, - None, - )); + let post_init_stmts = post_init::codegen(&app, analysis); - let attrs = &task.attrs; - let cfgs = &task.cfgs; - let task_alias = &ctxt.tasks[name].alias; - let (baseline, baseline_arg) = match () { - #[cfg(feature = "timer-queue")] - () => { - let baseline = &ctxt.baseline; - (quote!(#baseline,), quote!(#baseline: &rtfm::Instant,)) - } - #[cfg(not(feature = "timer-queue"))] - () => (quote!(), quote!()), - }; - let pats = tuple_pat(inputs); - items.push(quote!( - // unsafe trampoline to deter end-users from calling this non-reentrant function - #(#attrs)* - #(#cfgs)* - unsafe fn #task_alias(#baseline_arg #(#inputs,)*) { - #[inline(always)] - #unsafety fn task(#baseline_arg #(#inputs,)*) { - #(#locals)* + let (mod_app_idle, root_idle, user_idle, user_idle_imports, call_idle) = + idle::codegen(app, analysis, extra); - #prelude - - #scheduled_let - - #(#stmts)* - } - - task(#baseline #pats) - } - )); - } - - 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![]; - - let device = &app.args.device; - for (level, dispatcher) in &analysis.dispatchers { - let ready_alias = ctxt.ident_gen.mk_ident(None, false); - let enum_alias = ctxt.ident_gen.mk_ident(None, false); - let capacity = mk_typenum_capacity(dispatcher.capacity, true); - - let variants = dispatcher - .tasks - .iter() - .map(|task| { - let task_ = &app.tasks[task]; - let cfgs = &task_.cfgs; - - quote!( - #(#cfgs)* - #task - ) - }) - .collect::<Vec<_>>(); - 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, - if cfg!(feature = "nightly") { - quote!(&mut #ready_alias) - } else { - quote!(#ready_alias.get_mut()) - }, - app, - None, - ); - - if cfg!(feature = "nightly") { - data.push(quote!( - #[doc = #symbol] - static mut #ready_alias: #ty = unsafe { #e::ReadyQueue::new_sc() }; - )); - } else { - data.push(quote!( - #[doc = #symbol] - static mut #ready_alias: #e::MaybeUninit<#ty> = #e::MaybeUninit::uninit(); - )); - } - data.push(quote!( - #[allow(dead_code)] - #[allow(non_camel_case_types)] - enum #enum_alias { #(#variants,)* } - - #resource - )); - - let arms = dispatcher - .tasks - .iter() - .map(|task| { - let task_ = &ctxt.tasks[task]; - let inputs = &task_.inputs; - let free = &task_.free_queue; - let alias = &task_.alias; - - let task__ = &app.tasks[task]; - let pats = tuple_pat(&task__.inputs); - let cfgs = &task__.cfgs; - - let baseline_let; - let call; - match () { - #[cfg(feature = "timer-queue")] - () => { - let scheduleds = &task_.scheduleds; - let scheduled = if cfg!(feature = "nightly") { - quote!(#scheduleds.get_unchecked(usize::from(index)).as_ptr()) - } else { - quote!(#scheduleds.get_ref().get_unchecked(usize::from(index))) - }; - - baseline_let = quote!( - let baseline = ptr::read(#scheduled); - ); - call = quote!(#alias(&baseline, #pats)); - } - #[cfg(not(feature = "timer-queue"))] - () => { - baseline_let = quote!(); - call = quote!(#alias(#pats)); - } - }; - - let (free_, input) = if cfg!(feature = "nightly") { - ( - quote!(#free), - quote!(#inputs.get_unchecked(usize::from(index)).as_ptr()), - ) - } else { - ( - quote!(#free.get_mut()), - quote!(#inputs.get_ref().get_unchecked(usize::from(index))), - ) - }; - - quote!( - #(#cfgs)* - #enum_alias::#task => { - #baseline_let - let input = ptr::read(#input); - #free_.split().0.enqueue_unchecked(index); - let (#pats) = input; - #call - } - ) - }) - .collect::<Vec<_>>(); - - let attrs = &dispatcher.attrs; - let interrupt = &dispatcher.interrupt; - let symbol = interrupt.to_string(); - let alias = ctxt.ident_gen.mk_ident(None, false); - let ready_alias_ = if cfg!(feature = "nightly") { - quote!(#ready_alias) - } else { - quote!(#ready_alias.get_mut()) - }; - dispatchers.push(quote!( - #(#attrs)* - #[export_name = #symbol] - unsafe fn #alias() { - use core::ptr; - - // check that this interrupt exists - let _ = #device::interrupt::#interrupt; - - rtfm::export::run(|| { - while let Some((task, index)) = #ready_alias_.split().1.dequeue() { - match task { - #(#arms)* - } - } - }); - } - )); - - ctxt.dispatchers.insert( - *level, - Dispatcher { - ready_queue: ready_alias, - enum_: 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 (name, task) in &ctxt.tasks { - let alias = &task.spawn_fn; - let task_ = &app.tasks[name]; - let cfgs = &task_.cfgs; - let free = &task.free_queue; - let level = task_.args.priority; - let dispatcher = &ctxt.dispatchers[&level]; - let ready = &dispatcher.ready_queue; - let enum_ = &dispatcher.enum_; - let dispatcher = &analysis.dispatchers[&level].interrupt; - let inputs = &task.inputs; - let args = &task_.inputs; - let ty = tuple_ty(args); - let pats = tuple_pat(args); - - let scheduleds_write = match () { - #[cfg(feature = "timer-queue")] - () => { - let scheduleds = &ctxt.tasks[name].scheduleds; - if cfg!(feature = "nightly") { - quote!( - ptr::write( - #scheduleds.get_unchecked_mut(usize::from(index)).as_mut_ptr(), - #baseline, - ); - ) - } else { - 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!(), - }; - - let input = if cfg!(feature = "nightly") { - quote!(#inputs.get_unchecked_mut(usize::from(index)).as_mut_ptr()) - } else { - quote!(#inputs.get_mut().get_unchecked_mut(usize::from(index))) - }; - items.push(quote!( - #[inline(always)] - #(#cfgs)* - unsafe fn #alias( - #baseline_arg - #priority: &rtfm::export::Priority, - #(#args,)* - ) -> Result<(), #ty> { - use core::ptr; - - use rtfm::Mutex; - - if let Some(index) = (#free { #priority }).lock(|f| f.split().1.dequeue()) { - ptr::write(#input, (#pats)); - #scheduleds_write - - #ready { #priority }.lock(|rq| { - rq.split().0.enqueue_unchecked((#enum_::#name, index)) - }); - - rtfm::pend(#device::Interrupt::#dispatcher); - - Ok(()) - } else { - Err((#pats)) - } - } + if user_init.is_some() { + mod_app_imports.push(quote!( + use super::init; )) } - - // 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 task_ = &app.tasks[task]; - let alias = &ctxt.tasks[task].spawn_fn; - let inputs = &task_.inputs; - let cfgs = &task_.cfgs; - 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] - #(#cfgs)* - 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 task_ = &ctxt.tasks[task]; - let free = &task_.free_queue; - let enum_ = &ctxt.schedule_enum; - let inputs = &task_.inputs; - let scheduleds = &task_.scheduleds; - let task__ = &app.tasks[task]; - let args = &task__.inputs; - let cfgs = &task__.cfgs; - let ty = tuple_ty(args); - let pats = tuple_pat(args); - - let input = if cfg!(feature = "nightly") { - quote!(#inputs.get_unchecked_mut(usize::from(index)).as_mut_ptr()) - } else { - quote!(#inputs.get_mut().get_unchecked_mut(usize::from(index))) - }; - - let scheduled = if cfg!(feature = "nightly") { - quote!(#scheduleds.get_unchecked_mut(usize::from(index)).as_mut_ptr()) - } else { - quote!(#scheduleds.get_mut().get_unchecked_mut(usize::from(index))) - }; - items.push(quote!( - #[inline(always)] - #(#cfgs)* - unsafe fn #alias( - #priority: &rtfm::export::Priority, - 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(#input, (#pats)); - ptr::write(#scheduled, instant); - - let nr = rtfm::export::NotReady { - instant, - index, - task: #enum_::#task, - }; - - ({#timer_queue { #priority }}).lock(|tq| tq.enqueue_unchecked(nr)); - - Ok(()) - } else { - Err((#pats)) - } - } + if user_idle.is_some() { + mod_app_imports.push(quote!( + use super::idle; )) } - // 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 task_ = &app.tasks[task]; - let inputs = &task_.inputs; - let cfgs = &task_.cfgs; - let ty = tuple_ty(inputs); - let pats = tuple_pat(inputs); + user.push(quote!( + #user_init - methods.push(quote!( - #[inline] - #(#cfgs)* - 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: &mut 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 variants = tasks - .iter() - .map(|task| { - let cfgs = &app.tasks[task].cfgs; - quote!( - #(#cfgs)* - #task - ) - }) - .collect::<Vec<_>>(); - let enum_ = &ctxt.schedule_enum; - items.push(quote!( - #[allow(dead_code)] - #[allow(non_camel_case_types)] - #[derive(Clone, Copy)] - enum #enum_ { #(#variants,)* } + #user_idle )); - let cap = mk_typenum_capacity(analysis.timer_queue.capacity, false); - let tq = &ctxt.timer_queue; - let symbol = format!("TIMER_QUEUE::{}", tq); - if cfg!(feature = "nightly") { - items.push(quote!( - #[doc = #symbol] - static mut #tq: rtfm::export::MaybeUninit<rtfm::export::TimerQueue<#enum_, #cap>> = - rtfm::export::MaybeUninit::uninit(); - )); - } else { - items.push(quote!( - #[doc = #symbol] - static mut #tq: - rtfm::export::MaybeUninit<rtfm::export::TimerQueue<#enum_, #cap>> = - rtfm::export::MaybeUninit::uninit(); - )); - } - - items.push(mk_resource( - ctxt, - &[], - tq, - quote!(rtfm::export::TimerQueue<#enum_, #cap>), - analysis.timer_queue.ceiling, - quote!(&mut *#tq.as_mut_ptr()), - app, - None, + imports.push(quote!( + #(#user_init_imports)* + #(#user_idle_imports)* )); - let priority = &ctxt.priority; - let device = &app.args.device; - let arms = tasks - .iter() - .map(|task| { - let task_ = &app.tasks[task]; - let level = task_.args.priority; - let cfgs = &task_.cfgs; - let dispatcher_ = &ctxt.dispatchers[&level]; - let tenum = &dispatcher_.enum_; - let ready = &dispatcher_.ready_queue; - let dispatcher = &analysis.dispatchers[&level].interrupt; - - quote!( - #(#cfgs)* - #enum_::#task => { - (#ready { #priority }).lock(|rq| { - rq.split().0.enqueue_unchecked((#tenum::#task, index)) - }); - - rtfm::pend(#device::Interrupt::#dispatcher); - } - ) - }) - .collect::<Vec<_>>(); + root.push(quote!( + #(#root_init)* - let logical_prio = analysis.timer_queue.priority; - let alias = ctxt.ident_gen.mk_ident(None, false); - items.push(quote!( - #[export_name = "SysTick"] - #[doc(hidden)] - unsafe fn #alias() { - use rtfm::Mutex; - - let ref #priority = rtfm::export::Priority::new(#logical_prio); - - rtfm::export::run(|| { - rtfm::export::sys_tick(#tq { #priority }, |task, index| { - match task { - #(#arms)* - } - }); - }) - } + #(#root_idle)* )); - quote!(#(#items)*) -} - -fn pre_init(ctxt: &Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream { - let mut exprs = vec![]; - - if !cfg!(feature = "nightly") { - // these are `MaybeUninit` arrays - for task in ctxt.tasks.values() { - let inputs = &task.inputs; - exprs.push(quote!(#inputs.write(core::mem::uninitialized());)) - } - - #[cfg(feature = "timer-queue")] - for task in ctxt.tasks.values() { - let scheduleds = &task.scheduleds; - exprs.push(quote!(#scheduleds.write(core::mem::uninitialized());)) - } + mod_app.push(quote!( + #mod_app_init - // these are `MaybeUninit` `ReadyQueue`s - for dispatcher in ctxt.dispatchers.values() { - let rq = &dispatcher.ready_queue; - exprs.push(quote!(#rq.write(rtfm::export::ReadyQueue::new_sc());)) - } - - // these are `MaybeUninit` `FreeQueue`s - for task in ctxt.tasks.values() { - let fq = &task.free_queue; - exprs.push(quote!(#fq.write(rtfm::export::FreeQueue::new_sc());)) - } - } - - // Initialize the timer queue - if !analysis.timer_queue.tasks.is_empty() { - let tq = &ctxt.timer_queue; - exprs.push(quote!(#tq.write(rtfm::export::TimerQueue::new(p.SYST));)); - } - - // Populate the `FreeQueue`s - for (name, task) in &ctxt.tasks { - let fq = &task.free_queue; - let fq_ = if cfg!(feature = "nightly") { - quote!(#fq) - } else { - quote!(#fq.get_mut()) - }; - let capacity = analysis.capacities[name]; - exprs.push(quote!( - for i in 0..#capacity { - #fq_.enqueue_unchecked(i); - } - )) - } - - let device = &app.args.device; - let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS); - for (handler, interrupt) in &app.interrupts { - let name = interrupt.args.binds(handler); - let priority = interrupt.args.priority; - exprs.push(quote!(p.NVIC.enable(#device::Interrupt::#name);)); - exprs.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];)); - exprs.push(quote!(p.NVIC.set_priority( - #device::Interrupt::#name, - ((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!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];)); - exprs.push(quote!(p.NVIC.set_priority( - #device::Interrupt::#name, - ((1 << #nvic_prio_bits) - #priority) << (8 - #nvic_prio_bits), - );)); - } - - // 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, - cfgs: &[Attribute], - 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; + #mod_app_idle + )); - let mut items = vec![]; + let main = util::suffixed("main"); + mains.push(quote!( + #[no_mangle] + unsafe extern "C" fn #main() -> ! { + let _TODO: () = (); - let path = if let Some(module) = module { - let doc = format!("`{}`", ty); - module.push(quote!( - #[allow(non_camel_case_types)] - #[doc = #doc] - #(#cfgs)* - pub struct #struct_<'a> { - #[doc(hidden)] - pub #priority: &'a rtfm::export::Priority, - } - )); + #(#assertion_stmts)* - quote!(resources::#struct_) - } else { - items.push(quote!( - #(#cfgs)* - struct #struct_<'a> { - #priority: &'a rtfm::export::Priority, - } - )); + #(#pre_init_stmts)* - quote!(#struct_) - }; + #call_init - items.push(quote!( - #(#cfgs)* - impl<'a> rtfm::Mutex for #path<'a> { - type T = #ty; + #(#post_init_stmts)* - #[inline] - fn lock<R, F>(&mut self, f: F) -> R - where - F: FnOnce(&mut Self::T) -> R, - { - unsafe { - rtfm::export::claim( - #ptr, - &self.#priority, - #ceiling, - #device::NVIC_PRIO_BITS, - f, - ) - } - } + #call_idle } )); - quote!(#(#items)*) -} + let (mod_app_resources, mod_resources, mod_resources_imports) = + resources::codegen(app, analysis, extra); -fn mk_capacity_literal(capacity: u8) -> LitInt { - LitInt::new(u64::from(capacity), IntSuffix::None, Span::call_site()) -} + let ( + mod_app_hardware_tasks, + root_hardware_tasks, + user_hardware_tasks, + user_hardware_tasks_imports, + ) = hardware_tasks::codegen(app, analysis, extra); -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 ( + mod_app_software_tasks, + root_software_tasks, + user_software_tasks, + user_software_tasks_imports, + ) = software_tasks::codegen(app, analysis, extra); - let ident = Ident::new(&format!("U{}", capacity), Span::call_site()); + let mod_app_dispatchers = dispatchers::codegen(app, analysis, extra); - quote!(rtfm::export::consts::#ident) -} + let mod_app_spawn = spawn::codegen(app, analysis, extra); -struct IdentGenerator { - call_count: u32, - rng: rand::rngs::SmallRng, -} + let mod_app_timer_queue = timer_queue::codegen(app, analysis, extra); -impl IdentGenerator { - fn new() -> IdentGenerator { - let elapsed = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + let mod_app_schedule = schedule::codegen(app, extra); - let secs = elapsed.as_secs(); - let nanos = elapsed.subsec_nanos(); + let user_imports = app.user_imports.clone(); + let user_code = app.user_code.clone(); + let name = &app.name; + let device = extra.device; + quote!( + #(#user)* - let mut seed: [u8; 16] = [0; 16]; + #(#user_hardware_tasks)* - for (i, v) in seed.iter_mut().take(8).enumerate() { - *v = ((secs >> (i * 8)) & 0xFF) as u8 - } + #(#user_software_tasks)* - for (i, v) in seed.iter_mut().skip(8).take(4).enumerate() { - *v = ((nanos >> (i * 8)) & 0xFF) as u8 - } + #(#root)* - let rng = rand::rngs::SmallRng::from_seed(seed); + #mod_resources - IdentGenerator { call_count: 0, rng } - } + #(#root_hardware_tasks)* - fn mk_ident(&mut self, name: Option<&str>, random: bool) -> Ident { - let s = if let Some(name) = name { - format!("{}_", name) - } else { - "__rtfm_internal_".to_string() - }; + #(#root_software_tasks)* - let mut s = format!("{}{}", s, self.call_count); - self.call_count += 1; + /// Implementation details + mod #name { + /// Always include the device crate which contains the vector table + use #device as _; + #(#imports)* + #(#user_imports)* - if random { - s.push('_'); + /// User code from within the module + #(#user_code)* + /// User code end - for i in 0..4 { - if i == 0 || self.rng.gen() { - s.push(('a' as u8 + self.rng.gen::<u8>() % 25) as char) - } else { - s.push(('0' as u8 + self.rng.gen::<u8>() % 10) as char) - } - } - } - Ident::new(&s, Span::call_site()) - } -} - -// `once = true` means that these locals will be called from a function that will run *once* -fn mk_locals(locals: &BTreeMap<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 cfgs = &static_.cfgs; - let expr = &static_.expr; - let ident = name; - let ty = &static_.ty; + #(#user_hardware_tasks_imports)* - quote!( - #[allow(non_snake_case)] - #(#cfgs)* - let #ident: &#lt mut #ty = { - #(#attrs)* - #(#cfgs)* - static mut #ident: #ty = #expr; + #(#user_software_tasks_imports)* - unsafe { &mut #ident } - }; - ) - }) - .collect::<Vec<_>>(); - - quote!(#(#locals)*) -} + #(#mod_resources_imports)* -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<_>>(); + /// app module + #(#mod_app)* - quote!(#(#pats,)*) - } -} + #(#mod_app_resources)* -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<_>>(); + #(#mod_app_hardware_tasks)* - quote!((#(#tys,)*)) - } -} + #(#mod_app_software_tasks)* -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -enum Kind { - Exception(Ident), - Idle, - Init, - Interrupt(Ident), - Task(Ident), -} + #(#mod_app_dispatchers)* -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(), - } - } + #(#mod_app_spawn)* - fn is_idle(&self) -> bool { - *self == Kind::Idle - } + #(#mod_app_timer_queue)* - fn is_init(&self) -> bool { - *self == Kind::Init - } + #(#mod_app_schedule)* - fn runs_once(&self) -> bool { - match *self { - Kind::Init | Kind::Idle => true, - _ => false, + #(#mains)* } - } + ) } diff --git a/macros/src/codegen/assertions.rs b/macros/src/codegen/assertions.rs new file mode 100644 index 00000000..4d9aae47 --- /dev/null +++ b/macros/src/codegen/assertions.rs @@ -0,0 +1,19 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +use crate::analyze::Analysis; + +/// Generates compile-time assertions that check that types implement the `Send` / `Sync` traits +pub fn codegen(analysis: &Analysis) -> Vec<TokenStream2> { + let mut stmts = vec![]; + + for ty in &analysis.send_types { + stmts.push(quote!(rtic::export::assert_send::<#ty>();)); + } + + for ty in &analysis.sync_types { + stmts.push(quote!(rtic::export::assert_sync::<#ty>();)); + } + + stmts +} diff --git a/macros/src/codegen/dispatchers.rs b/macros/src/codegen/dispatchers.rs new file mode 100644 index 00000000..300aa996 --- /dev/null +++ b/macros/src/codegen/dispatchers.rs @@ -0,0 +1,155 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtic_syntax::ast::App; + +use crate::{analyze::Analysis, check::Extra, codegen::util}; + +/// Generates task dispatchers +pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec<TokenStream2> { + let mut items = vec![]; + + let interrupts = &analysis.interrupts; + + for (&level, channel) in &analysis.channels { + let mut stmts = vec![]; + + let variants = channel + .tasks + .iter() + .map(|name| { + let cfgs = &app.software_tasks[name].cfgs; + + quote!( + #(#cfgs)* + #name + ) + }) + .collect::<Vec<_>>(); + + let doc = format!( + "Software tasks to be dispatched at priority level {}", + level, + ); + let t = util::spawn_t_ident(level); + items.push(quote!( + #[allow(non_camel_case_types)] + #[derive(Clone, Copy)] + #[doc = #doc] + enum #t { + #(#variants,)* + } + )); + + let n = util::capacity_typenum(channel.capacity, true); + let rq = util::rq_ident(level); + let (rq_ty, rq_expr) = { + ( + quote!(rtic::export::SCRQ<#t, #n>), + quote!(rtic::export::Queue(unsafe { + rtic::export::iQueue::u8_sc() + })), + ) + }; + + let doc = format!( + "Queue of tasks ready to be dispatched at priority level {}", + level + ); + items.push(quote!( + #[doc = #doc] + static mut #rq: #rq_ty = #rq_expr; + )); + + if let Some(ceiling) = channel.ceiling { + items.push(quote!( + struct #rq<'a> { + priority: &'a rtic::export::Priority, + } + )); + + items.push(util::impl_mutex( + extra, + &[], + false, + &rq, + rq_ty, + ceiling, + quote!(&mut #rq), + )); + } + + let arms = channel + .tasks + .iter() + .map(|name| { + let task = &app.software_tasks[name]; + let cfgs = &task.cfgs; + let fq = util::fq_ident(name); + let inputs = util::inputs_ident(name); + let (_, tupled, pats, _) = util::regroup_inputs(&task.inputs); + + let (let_instant, instant) = if app.uses_schedule() { + let instants = util::instants_ident(name); + + ( + quote!( + let instant = + #instants.get_unchecked(usize::from(index)).as_ptr().read(); + ), + quote!(, instant), + ) + } else { + (quote!(), quote!()) + }; + + let locals_new = if task.locals.is_empty() { + quote!() + } else { + quote!(#name::Locals::new(),) + }; + + quote!( + #(#cfgs)* + #t::#name => { + let #tupled = + #inputs.get_unchecked(usize::from(index)).as_ptr().read(); + #let_instant + #fq.split().0.enqueue_unchecked(index); + let priority = &rtic::export::Priority::new(PRIORITY); + crate::#name( + #locals_new + #name::Context::new(priority #instant) + #(,#pats)* + ) + } + ) + }) + .collect::<Vec<_>>(); + + stmts.push(quote!( + while let Some((task, index)) = #rq.split().1.dequeue() { + match task { + #(#arms)* + } + } + )); + + let doc = format!("Interrupt handler to dispatch tasks at priority {}", level); + let interrupt = util::suffixed(&interrupts[&level].to_string()); + items.push(quote!( + #[allow(non_snake_case)] + #[doc = #doc] + #[no_mangle] + unsafe fn #interrupt() { + /// The priority of this interrupt handler + const PRIORITY: u8 = #level; + + rtic::export::run(PRIORITY, || { + #(#stmts)* + }); + } + )); + } + + items +} diff --git a/macros/src/codegen/hardware_tasks.rs b/macros/src/codegen/hardware_tasks.rs new file mode 100644 index 00000000..25f1df41 --- /dev/null +++ b/macros/src/codegen/hardware_tasks.rs @@ -0,0 +1,134 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::{format_ident, quote}; +use rtic_syntax::{ast::App, Context}; + +use crate::{ + analyze::Analysis, + check::Extra, + codegen::{locals, module, resources_struct}, +}; + +/// Generate support code for hardware tasks (`#[exception]`s and `#[interrupt]`s) +pub fn codegen( + app: &App, + analysis: &Analysis, + extra: &Extra, +) -> ( + // mod_app_hardware_tasks -- interrupt handlers and `${task}Resources` constructors + Vec<TokenStream2>, + // root_hardware_tasks -- items that must be placed in the root of the crate: + // - `${task}Locals` structs + // - `${task}Resources` structs + // - `${task}` modules + Vec<TokenStream2>, + // user_hardware_tasks -- the `#[task]` functions written by the user + Vec<TokenStream2>, + // user_hardware_tasks_imports -- the imports for `#[task]` functions written by the user + Vec<TokenStream2>, +) { + let mut mod_app = vec![]; + let mut root = vec![]; + let mut user_tasks = vec![]; + let mut hardware_tasks_imports = vec![]; + + for (name, task) in &app.hardware_tasks { + let (let_instant, instant) = if app.uses_schedule() { + let m = extra.monotonic(); + + ( + Some(quote!(let instant = <#m as rtic::Monotonic>::now();)), + Some(quote!(, instant)), + ) + } else { + (None, None) + }; + + let locals_new = if task.locals.is_empty() { + quote!() + } else { + quote!(#name::Locals::new(),) + }; + + let symbol = task.args.binds.clone(); + let priority = task.args.priority; + + mod_app.push(quote!( + #[allow(non_snake_case)] + #[no_mangle] + unsafe fn #symbol() { + const PRIORITY: u8 = #priority; + + #let_instant + + rtic::export::run(PRIORITY, || { + crate::#name( + #locals_new + #name::Context::new(&rtic::export::Priority::new(PRIORITY) #instant) + ) + }); + } + )); + + let mut needs_lt = false; + + // `${task}Resources` + if !task.args.resources.is_empty() { + let (item, constructor) = resources_struct::codegen( + Context::HardwareTask(name), + priority, + &mut needs_lt, + app, + analysis, + ); + + // Add resources to imports + let name_res = format_ident!("{}Resources", name); + hardware_tasks_imports.push(quote!( + #[allow(non_snake_case)] + use super::#name_res; + )); + + root.push(item); + + mod_app.push(constructor); + } + + root.push(module::codegen( + Context::HardwareTask(name), + needs_lt, + app, + extra, + )); + + // `${task}Locals` + let mut locals_pat = None; + if !task.locals.is_empty() { + let (struct_, pat) = locals::codegen(Context::HardwareTask(name), &task.locals, app); + + root.push(struct_); + locals_pat = Some(pat); + } + + let attrs = &task.attrs; + let context = &task.context; + let stmts = &task.stmts; + let locals_pat = locals_pat.iter(); + user_tasks.push(quote!( + #(#attrs)* + #[allow(non_snake_case)] + fn #name(#(#locals_pat,)* #context: #name::Context) { + use rtic::Mutex as _; + + #(#stmts)* + } + )); + + hardware_tasks_imports.push(quote!( + #(#attrs)* + #[allow(non_snake_case)] + use super::#name; + )); + } + + (mod_app, root, user_tasks, hardware_tasks_imports) +} diff --git a/macros/src/codegen/idle.rs b/macros/src/codegen/idle.rs new file mode 100644 index 00000000..2e2932d7 --- /dev/null +++ b/macros/src/codegen/idle.rs @@ -0,0 +1,104 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::{format_ident, quote}; +use rtic_syntax::{ast::App, Context}; + +use crate::{ + analyze::Analysis, + check::Extra, + codegen::{locals, module, resources_struct}, +}; + +/// Generates support code for `#[idle]` functions +pub fn codegen( + app: &App, + analysis: &Analysis, + extra: &Extra, +) -> ( + // mod_app_idle -- the `${idle}Resources` constructor + Option<TokenStream2>, + // root_idle -- items that must be placed in the root of the crate: + // - the `${idle}Locals` struct + // - the `${idle}Resources` struct + // - the `${idle}` module, which contains types like `${idle}::Context` + Vec<TokenStream2>, + // user_idle + Option<TokenStream2>, + // user_idle_imports + Vec<TokenStream2>, + // call_idle + TokenStream2, +) { + if app.idles.len() > 0 { + let idle = &app.idles.first().unwrap(); + let mut needs_lt = false; + let mut mod_app = None; + let mut root_idle = vec![]; + let mut locals_pat = None; + let mut locals_new = None; + + let mut user_idle_imports = vec![]; + + let name = &idle.name; + + if !idle.args.resources.is_empty() { + let (item, constructor) = + resources_struct::codegen(Context::Idle, 0, &mut needs_lt, app, analysis); + + root_idle.push(item); + mod_app = Some(constructor); + + let name_resource = format_ident!("{}Resources", name); + user_idle_imports.push(quote!( + #[allow(non_snake_case)] + use super::#name_resource; + )); + } + + if !idle.locals.is_empty() { + let (locals, pat) = locals::codegen(Context::Idle, &idle.locals, app); + + locals_new = Some(quote!(#name::Locals::new())); + locals_pat = Some(pat); + root_idle.push(locals); + } + + root_idle.push(module::codegen(Context::Idle, needs_lt, app, extra)); + + let attrs = &idle.attrs; + let context = &idle.context; + let stmts = &idle.stmts; + let locals_pat = locals_pat.iter(); + let user_idle = Some(quote!( + #(#attrs)* + #[allow(non_snake_case)] + fn #name(#(#locals_pat,)* #context: #name::Context) -> ! { + use rtic::Mutex as _; + + #(#stmts)* + } + )); + user_idle_imports.push(quote!( + #(#attrs)* + #[allow(non_snake_case)] + use super::#name; + )); + + let locals_new = locals_new.iter(); + let call_idle = quote!(crate::#name( + #(#locals_new,)* + #name::Context::new(&rtic::export::Priority::new(0)) + )); + + (mod_app, root_idle, user_idle, user_idle_imports, call_idle) + } else { + ( + None, + vec![], + None, + vec![], + quote!(loop { + rtic::export::wfi() + }), + ) + } +} diff --git a/macros/src/codegen/init.rs b/macros/src/codegen/init.rs new file mode 100644 index 00000000..8942439b --- /dev/null +++ b/macros/src/codegen/init.rs @@ -0,0 +1,125 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::{format_ident, quote}; +use rtic_syntax::{ast::App, Context}; + +use crate::{ + analyze::Analysis, + check::Extra, + codegen::{locals, module, resources_struct, util}, +}; + +/// Generates support code for `#[init]` functions +pub fn codegen( + app: &App, + analysis: &Analysis, + extra: &Extra, +) -> ( + // mod_app_idle -- the `${init}Resources` constructor + Option<TokenStream2>, + // root_init -- items that must be placed in the root of the crate: + // - the `${init}Locals` struct + // - the `${init}Resources` struct + // - the `${init}LateResources` struct + // - the `${init}` module, which contains types like `${init}::Context` + Vec<TokenStream2>, + // user_init -- the `#[init]` function written by the user + Option<TokenStream2>, + // user_init_imports -- the imports for `#[init]` functio written by the user + Vec<TokenStream2>, + // call_init -- the call to the user `#[init]` if there's one + Option<TokenStream2>, +) { + if app.inits.len() > 0 { + let init = &app.inits.first().unwrap(); + let mut needs_lt = false; + let name = &init.name; + + let mut root_init = vec![]; + + let late_fields = analysis + .late_resources + .iter() + .flat_map(|resources| { + resources.iter().map(|name| { + let ty = &app.late_resources[name].ty; + let cfgs = &app.late_resources[name].cfgs; + + quote!( + #(#cfgs)* + pub #name: #ty + ) + }) + }) + .collect::<Vec<_>>(); + + let mut user_init_imports = vec![]; + let late_resources = util::late_resources_ident(&name); + + root_init.push(quote!( + /// Resources initialized at runtime + #[allow(non_snake_case)] + pub struct #late_resources { + #(#late_fields),* + } + )); + + let name_late = format_ident!("{}LateResources", name); + user_init_imports.push(quote!( + #[allow(non_snake_case)] + use super::#name_late; + )); + + let mut locals_pat = None; + let mut locals_new = None; + if !init.locals.is_empty() { + let (struct_, pat) = locals::codegen(Context::Init, &init.locals, app); + + locals_new = Some(quote!(#name::Locals::new())); + locals_pat = Some(pat); + root_init.push(struct_); + } + + let context = &init.context; + let attrs = &init.attrs; + let stmts = &init.stmts; + let locals_pat = locals_pat.iter(); + let user_init = Some(quote!( + #(#attrs)* + #[allow(non_snake_case)] + fn #name(#(#locals_pat,)* #context: #name::Context) -> #name::LateResources { + #(#stmts)* + } + )); + user_init_imports.push(quote!( + #(#attrs)* + #[allow(non_snake_case)] + use super::#name; + )); + + let mut mod_app = None; + if !init.args.resources.is_empty() { + let (item, constructor) = + resources_struct::codegen(Context::Init, 0, &mut needs_lt, app, analysis); + + root_init.push(item); + mod_app = Some(constructor); + + let name_late = format_ident!("{}Resources", name); + user_init_imports.push(quote!( + #[allow(non_snake_case)] + use super::#name_late; + )); + } + + let locals_new = locals_new.iter(); + let call_init = Some( + quote!(let late = crate::#name(#(#locals_new,)* #name::Context::new(core.into()));), + ); + + root_init.push(module::codegen(Context::Init, needs_lt, app, extra)); + + (mod_app, root_init, user_init, user_init_imports, call_init) + } else { + (None, vec![], None, vec![], None) + } +} diff --git a/macros/src/codegen/locals.rs b/macros/src/codegen/locals.rs new file mode 100644 index 00000000..336c0b21 --- /dev/null +++ b/macros/src/codegen/locals.rs @@ -0,0 +1,94 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtic_syntax::{ + ast::{App, Local}, + Context, Map, +}; + +use crate::codegen::util; + +pub fn codegen( + ctxt: Context, + locals: &Map<Local>, + app: &App, +) -> ( + // locals + TokenStream2, + // pat + TokenStream2, +) { + assert!(!locals.is_empty()); + + let runs_once = ctxt.runs_once(); + let ident = util::locals_ident(ctxt, app); + + let mut lt = None; + let mut fields = vec![]; + let mut items = vec![]; + let mut names = vec![]; + let mut values = vec![]; + let mut pats = vec![]; + let mut has_cfgs = false; + + for (name, local) in locals { + let lt = if runs_once { + quote!('static) + } else { + lt = Some(quote!('a)); + quote!('a) + }; + + let cfgs = &local.cfgs; + has_cfgs |= !cfgs.is_empty(); + + let expr = &local.expr; + let ty = &local.ty; + fields.push(quote!( + #(#cfgs)* + #name: &#lt mut #ty + )); + items.push(quote!( + #(#cfgs)* + static mut #name: #ty = #expr + )); + values.push(quote!( + #(#cfgs)* + #name: &mut #name + )); + names.push(name); + pats.push(quote!( + #(#cfgs)* + #name + )); + } + + if lt.is_some() && has_cfgs { + fields.push(quote!(__marker__: core::marker::PhantomData<&'a mut ()>)); + values.push(quote!(__marker__: core::marker::PhantomData)); + } + + let locals = quote!( + #[allow(non_snake_case)] + #[doc(hidden)] + pub struct #ident<#lt> { + #(#fields),* + } + + impl<#lt> #ident<#lt> { + #[inline(always)] + unsafe fn new() -> Self { + #(#items;)* + + #ident { + #(#values),* + } + } + } + ); + + let ident = ctxt.ident(app); + ( + locals, + quote!(#ident::Locals { #(#pats,)* .. }: #ident::Locals), + ) +} diff --git a/macros/src/codegen/module.rs b/macros/src/codegen/module.rs new file mode 100644 index 00000000..2e51e7db --- /dev/null +++ b/macros/src/codegen/module.rs @@ -0,0 +1,330 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtic_syntax::{ast::App, Context}; + +use crate::{check::Extra, codegen::util}; + +pub fn codegen(ctxt: Context, resources_tick: bool, app: &App, extra: &Extra) -> TokenStream2 { + let mut items = vec![]; + let mut fields = vec![]; + let mut values = vec![]; + + let name = ctxt.ident(app); + + let mut needs_instant = false; + let mut lt = None; + match ctxt { + Context::Init => { + if app.uses_schedule() { + let m = extra.monotonic(); + + fields.push(quote!( + /// System start time = `Instant(0 /* cycles */)` + pub start: <#m as rtic::Monotonic>::Instant + )); + + values.push(quote!(start: <#m as rtic::Monotonic>::zero())); + + fields.push(quote!( + /// Core (Cortex-M) peripherals minus the SysTick + pub core: rtic::Peripherals + )); + } else { + fields.push(quote!( + /// Core (Cortex-M) peripherals + pub core: rtic::export::Peripherals + )); + } + + if extra.peripherals { + let device = extra.device; + + fields.push(quote!( + /// Device peripherals + pub device: #device::Peripherals + )); + + values.push(quote!(device: #device::Peripherals::steal())); + } + + lt = Some(quote!('a)); + fields.push(quote!( + /// Critical section token for init + pub cs: rtic::export::CriticalSection<#lt> + )); + + values.push(quote!(cs: rtic::export::CriticalSection::new())); + + values.push(quote!(core)); + } + + Context::Idle => {} + + Context::HardwareTask(..) => { + if app.uses_schedule() { + let m = extra.monotonic(); + + fields.push(quote!( + /// Time at which this handler started executing + pub start: <#m as rtic::Monotonic>::Instant + )); + + values.push(quote!(start: instant)); + + needs_instant = true; + } + } + + Context::SoftwareTask(..) => { + if app.uses_schedule() { + let m = extra.monotonic(); + + fields.push(quote!( + /// The time at which this task was scheduled to run + pub scheduled: <#m as rtic::Monotonic>::Instant + )); + + values.push(quote!(scheduled: instant)); + + needs_instant = true; + } + } + } + + if ctxt.has_locals(app) { + let ident = util::locals_ident(ctxt, app); + items.push(quote!( + #[doc(inline)] + pub use super::#ident as Locals; + )); + } + + if ctxt.has_resources(app) { + let ident = util::resources_ident(ctxt, app); + let lt = if resources_tick { + lt = Some(quote!('a)); + Some(quote!('a)) + } else { + None + }; + + items.push(quote!( + #[doc(inline)] + pub use super::#ident as Resources; + )); + + fields.push(quote!( + /// Resources this task has access to + pub resources: Resources<#lt> + )); + + let priority = if ctxt.is_init() { + None + } else { + Some(quote!(priority)) + }; + values.push(quote!(resources: Resources::new(#priority))); + } + + if ctxt.uses_schedule(app) { + let doc = "Tasks that can be `schedule`-d from this context"; + if ctxt.is_init() { + items.push(quote!( + #[doc = #doc] + #[derive(Clone, Copy)] + pub struct Schedule { + _not_send: core::marker::PhantomData<*mut ()>, + } + )); + + fields.push(quote!( + #[doc = #doc] + pub schedule: Schedule + )); + + values.push(quote!( + schedule: Schedule { _not_send: core::marker::PhantomData } + )); + } else { + lt = Some(quote!('a)); + + items.push(quote!( + #[doc = #doc] + #[derive(Clone, Copy)] + pub struct Schedule<'a> { + priority: &'a rtic::export::Priority, + } + + impl<'a> Schedule<'a> { + #[doc(hidden)] + #[inline(always)] + pub unsafe fn priority(&self) -> &rtic::export::Priority { + &self.priority + } + } + )); + + fields.push(quote!( + #[doc = #doc] + pub schedule: Schedule<'a> + )); + + values.push(quote!( + schedule: Schedule { priority } + )); + } + } + + if ctxt.uses_spawn(app) { + let doc = "Tasks that can be `spawn`-ed from this context"; + if ctxt.is_init() { + fields.push(quote!( + #[doc = #doc] + pub spawn: Spawn + )); + + items.push(quote!( + #[doc = #doc] + #[derive(Clone, Copy)] + pub struct Spawn { + _not_send: core::marker::PhantomData<*mut ()>, + } + )); + + values.push(quote!(spawn: Spawn { _not_send: core::marker::PhantomData })); + } else { + lt = Some(quote!('a)); + + fields.push(quote!( + #[doc = #doc] + pub spawn: Spawn<'a> + )); + + let mut instant_method = None; + if ctxt.is_idle() { + items.push(quote!( + #[doc = #doc] + #[derive(Clone, Copy)] + pub struct Spawn<'a> { + priority: &'a rtic::export::Priority, + } + )); + + values.push(quote!(spawn: Spawn { priority })); + } else { + let instant_field = if app.uses_schedule() { + let m = extra.monotonic(); + + needs_instant = true; + instant_method = Some(quote!( + pub unsafe fn instant(&self) -> <#m as rtic::Monotonic>::Instant { + self.instant + } + )); + Some(quote!(instant: <#m as rtic::Monotonic>::Instant,)) + } else { + None + }; + + items.push(quote!( + /// Tasks that can be spawned from this context + #[derive(Clone, Copy)] + pub struct Spawn<'a> { + #instant_field + priority: &'a rtic::export::Priority, + } + )); + + let _instant = if needs_instant { + Some(quote!(, instant)) + } else { + None + }; + values.push(quote!( + spawn: Spawn { priority #_instant } + )); + } + + items.push(quote!( + impl<'a> Spawn<'a> { + #[doc(hidden)] + #[inline(always)] + pub unsafe fn priority(&self) -> &rtic::export::Priority { + self.priority + } + + #instant_method + } + )); + } + } + + if let Context::Init = ctxt { + let init = &app.inits.first().unwrap(); + let late_resources = util::late_resources_ident(&init.name); + + items.push(quote!( + #[doc(inline)] + pub use super::#late_resources as LateResources; + )); + } + + let doc = match ctxt { + Context::Idle => "Idle loop", + Context::Init => "Initialization function", + Context::HardwareTask(_) => "Hardware task", + Context::SoftwareTask(_) => "Software task", + }; + + let core = if ctxt.is_init() { + if app.uses_schedule() { + Some(quote!(core: rtic::Peripherals,)) + } else { + Some(quote!(core: rtic::export::Peripherals,)) + } + } else { + None + }; + + let priority = if ctxt.is_init() { + None + } else { + Some(quote!(priority: &#lt rtic::export::Priority)) + }; + + let instant = if needs_instant { + let m = extra.monotonic(); + + Some(quote!(, instant: <#m as rtic::Monotonic>::Instant)) + } else { + None + }; + + items.push(quote!( + /// Execution context + pub struct Context<#lt> { + #(#fields,)* + } + + impl<#lt> Context<#lt> { + #[inline(always)] + pub unsafe fn new(#core #priority #instant) -> Self { + Context { + #(#values,)* + } + } + } + )); + + if !items.is_empty() { + quote!( + #[allow(non_snake_case)] + #[doc = #doc] + pub mod #name { + #(#items)* + } + ) + } else { + quote!() + } +} diff --git a/macros/src/codegen/post_init.rs b/macros/src/codegen/post_init.rs new file mode 100644 index 00000000..c35c6976 --- /dev/null +++ b/macros/src/codegen/post_init.rs @@ -0,0 +1,31 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtic_syntax::ast::App; + +use crate::analyze::Analysis; + +/// Generates code that runs after `#[init]` returns +pub fn codegen(app: &App, analysis: &Analysis) -> Vec<TokenStream2> { + let mut stmts = vec![]; + + // Initialize late resources + if analysis.late_resources.len() > 0 { + // BTreeSet wrapped in a vector + for name in analysis.late_resources.first().unwrap() { + // If it's live + let cfgs = app.late_resources[name].cfgs.clone(); + if analysis.locations.get(name).is_some() { + // Need to also include the cfgs + stmts.push(quote!( + #(#cfgs)* + #name.as_mut_ptr().write(late.#name); + )); + } + } + } + + // Enable the interrupts -- this completes the `init`-ialization phase + stmts.push(quote!(rtic::export::interrupt::enable();)); + + stmts +} diff --git a/macros/src/codegen/pre_init.rs b/macros/src/codegen/pre_init.rs new file mode 100644 index 00000000..9c5f35ec --- /dev/null +++ b/macros/src/codegen/pre_init.rs @@ -0,0 +1,109 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtic_syntax::ast::App; + +use crate::{analyze::Analysis, check::Extra, codegen::util}; + +/// Generates code that runs before `#[init]` +pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec<TokenStream2> { + let mut stmts = vec![]; + + // Disable interrupts -- `init` must run with interrupts disabled + stmts.push(quote!(rtic::export::interrupt::disable();)); + + // Populate the FreeQueue + for fq in &analysis.free_queues { + // Get the task name + let name = fq.0; + let task = &app.software_tasks[name]; + let cap = task.args.capacity; + + let fq_ident = util::fq_ident(name); + + stmts.push(quote!( + (0..#cap).for_each(|i| #fq_ident.enqueue_unchecked(i)); + )); + } + + stmts.push(quote!( + // To set the variable in cortex_m so the peripherals cannot be taken multiple times + let mut core: rtic::export::Peripherals = rtic::export::Peripherals::steal().into(); + )); + + let device = extra.device; + let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS); + + // Unmask interrupts and set their priorities + for (&priority, name) in analysis + .interrupts + .iter() + .chain(app.hardware_tasks.values().flat_map(|task| { + if !util::is_exception(&task.args.binds) { + Some((&task.args.priority, &task.args.binds)) + } else { + // We do exceptions in another pass + None + } + })) + { + // Compile time assert that this priority is supported by the device + stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];)); + + // NOTE this also checks that the interrupt exists in the `Interrupt` enumeration + let interrupt = util::interrupt_ident(); + stmts.push(quote!( + core.NVIC.set_priority( + #device::#interrupt::#name, + rtic::export::logical2hw(#priority, #nvic_prio_bits), + ); + )); + + // NOTE unmask the interrupt *after* setting its priority: changing the priority of a pended + // interrupt is implementation defined + stmts.push(quote!(rtic::export::NVIC::unmask(#device::#interrupt::#name);)); + } + + // Set exception priorities + for (name, priority) in app.hardware_tasks.values().filter_map(|task| { + if util::is_exception(&task.args.binds) { + Some((&task.args.binds, task.args.priority)) + } else { + None + } + }) { + // Compile time assert that this priority is supported by the device + stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];)); + + stmts.push(quote!(core.SCB.set_priority( + rtic::export::SystemHandler::#name, + rtic::export::logical2hw(#priority, #nvic_prio_bits), + );)); + } + + // Initialize the SysTick if there exist a TimerQueue + if let Some(tq) = analysis.timer_queues.first() { + let priority = tq.priority; + + // Compile time assert that this priority is supported by the device + stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];)); + + stmts.push(quote!(core.SCB.set_priority( + rtic::export::SystemHandler::SysTick, + rtic::export::logical2hw(#priority, #nvic_prio_bits), + );)); + + stmts.push(quote!( + core.SYST.set_clock_source(rtic::export::SystClkSource::Core); + core.SYST.enable_counter(); + core.DCB.enable_trace(); + )); + } + + // If there's no user `#[idle]` then optimize returning from interrupt handlers + if app.idles.is_empty() { + // Set SLEEPONEXIT bit to enter sleep mode when returning from ISR + stmts.push(quote!(core.SCB.scr.modify(|r| r | 1 << 1);)); + } + + stmts +} diff --git a/macros/src/codegen/resources.rs b/macros/src/codegen/resources.rs new file mode 100644 index 00000000..38ea5245 --- /dev/null +++ b/macros/src/codegen/resources.rs @@ -0,0 +1,122 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtic_syntax::{analyze::Ownership, ast::App}; + +use crate::{analyze::Analysis, check::Extra, codegen::util}; + +/// Generates `static [mut]` variables and resource proxies +pub fn codegen( + app: &App, + analysis: &Analysis, + extra: &Extra, +) -> ( + // mod_app -- the `static [mut]` variables behind the proxies + Vec<TokenStream2>, + // mod_resources -- the `resources` module + TokenStream2, + // mod_resources_imports -- the `resources` module imports + Vec<TokenStream2>, +) { + let mut mod_app = vec![]; + let mut mod_resources = vec![]; + let mut mod_resources_imports = vec![]; + + for (name, res, expr, _) in app.resources(analysis) { + let cfgs = &res.cfgs; + let ty = &res.ty; + + { + let section = if expr.is_none() { + util::link_section_uninit(true) + } else { + None + }; + + let (ty, expr) = if let Some(expr) = expr { + (quote!(#ty), quote!(#expr)) + } else { + ( + quote!(core::mem::MaybeUninit<#ty>), + quote!(core::mem::MaybeUninit::uninit()), + ) + }; + + let attrs = &res.attrs; + mod_app.push(quote!( + #[allow(non_upper_case_globals)] + #(#attrs)* + #(#cfgs)* + #section + static mut #name: #ty = #expr; + )); + } + + if let Some(Ownership::Contended { ceiling }) = analysis.ownerships.get(name) { + mod_resources.push(quote!( + #[allow(non_camel_case_types)] + #(#cfgs)* + pub struct #name<'a> { + priority: &'a Priority, + } + + #(#cfgs)* + impl<'a> #name<'a> { + #[inline(always)] + pub unsafe fn new(priority: &'a Priority) -> Self { + #name { priority } + } + + #[inline(always)] + pub unsafe fn priority(&self) -> &Priority { + self.priority + } + } + )); + + let ptr = if expr.is_none() { + quote!( + #(#cfgs)* + #name.as_mut_ptr() + ) + } else { + quote!( + #(#cfgs)* + &mut #name + ) + }; + + mod_resources_imports.push(quote!( + #[allow(non_camel_case_types)] + #(#cfgs)* + use super::resources::#name; + )); + + mod_app.push(util::impl_mutex( + extra, + cfgs, + true, + name, + quote!(#ty), + *ceiling, + ptr, + )); + } + } + + let mod_resources = if mod_resources.is_empty() { + quote!() + } else { + // Also import the resource module + mod_resources_imports.push(quote!( + use super::resources; + )); + + quote!(mod resources { + use rtic::export::Priority; + + #(#mod_resources)* + }) + }; + + (mod_app, mod_resources, mod_resources_imports) +} diff --git a/macros/src/codegen/resources_struct.rs b/macros/src/codegen/resources_struct.rs new file mode 100644 index 00000000..92d5b666 --- /dev/null +++ b/macros/src/codegen/resources_struct.rs @@ -0,0 +1,177 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtic_syntax::{ast::App, Context}; + +use crate::{analyze::Analysis, codegen::util}; + +pub fn codegen( + ctxt: Context, + priority: u8, + needs_lt: &mut bool, + app: &App, + analysis: &Analysis, +) -> (TokenStream2, TokenStream2) { + let mut lt = None; + + let resources = match ctxt { + Context::Init => &app.inits.first().unwrap().args.resources, + Context::Idle => &app.idles.first().unwrap().args.resources, + Context::HardwareTask(name) => &app.hardware_tasks[name].args.resources, + Context::SoftwareTask(name) => &app.software_tasks[name].args.resources, + }; + + let mut fields = vec![]; + let mut values = vec![]; + let mut has_cfgs = false; + + for (name, access) in resources { + let (res, expr) = app.resource(name).expect("UNREACHABLE"); + + let cfgs = &res.cfgs; + has_cfgs |= !cfgs.is_empty(); + + let mut_ = if access.is_exclusive() { + Some(quote!(mut)) + } else { + None + }; + let ty = &res.ty; + + if ctxt.is_init() { + if !analysis.ownerships.contains_key(name) { + // Owned by `init` + fields.push(quote!( + #(#cfgs)* + pub #name: &'static #mut_ #ty + )); + + values.push(quote!( + #(#cfgs)* + #name: &#mut_ #name + )); + } else { + // Owned by someone else + lt = Some(quote!('a)); + + fields.push(quote!( + #(#cfgs)* + pub #name: &'a mut #ty + )); + + values.push(quote!( + #(#cfgs)* + #name: &mut #name + )); + } + } else { + let ownership = &analysis.ownerships[name]; + + if ownership.needs_lock(priority) { + if mut_.is_none() { + lt = Some(quote!('a)); + + fields.push(quote!( + #(#cfgs)* + pub #name: &'a #ty + )); + } else { + // Resource proxy + lt = Some(quote!('a)); + + fields.push(quote!( + #(#cfgs)* + pub #name: resources::#name<'a> + )); + + values.push(quote!( + #(#cfgs)* + #name: resources::#name::new(priority) + + )); + + continue; + } + } else { + let lt = if ctxt.runs_once() { + quote!('static) + } else { + lt = Some(quote!('a)); + quote!('a) + }; + + if ownership.is_owned() || mut_.is_none() { + fields.push(quote!( + #(#cfgs)* + pub #name: &#lt #mut_ #ty + )); + } else { + fields.push(quote!( + #(#cfgs)* + pub #name: &#lt mut #ty + )); + } + } + + let is_late = expr.is_none(); + if is_late { + let expr = if mut_.is_some() { + quote!(&mut *#name.as_mut_ptr()) + } else { + quote!(&*#name.as_ptr()) + }; + + values.push(quote!( + #(#cfgs)* + #name: #expr + )); + } else { + values.push(quote!( + #(#cfgs)* + #name: &#mut_ #name + )); + } + } + } + + if lt.is_some() { + *needs_lt = true; + + // The struct could end up empty due to `cfg`s leading to an error due to `'a` being unused + if has_cfgs { + fields.push(quote!( + #[doc(hidden)] + pub __marker__: core::marker::PhantomData<&'a ()> + )); + + values.push(quote!(__marker__: core::marker::PhantomData)) + } + } + + let doc = format!("Resources `{}` has access to", ctxt.ident(app)); + let ident = util::resources_ident(ctxt, app); + let item = quote!( + #[allow(non_snake_case)] + #[doc = #doc] + pub struct #ident<#lt> { + #(#fields,)* + } + ); + + let arg = if ctxt.is_init() { + None + } else { + Some(quote!(priority: &#lt rtic::export::Priority)) + }; + let constructor = quote!( + impl<#lt> #ident<#lt> { + #[inline(always)] + pub unsafe fn new(#arg) -> Self { + #ident { + #(#values,)* + } + } + } + ); + + (item, constructor) +} diff --git a/macros/src/codegen/schedule.rs b/macros/src/codegen/schedule.rs new file mode 100644 index 00000000..5a887496 --- /dev/null +++ b/macros/src/codegen/schedule.rs @@ -0,0 +1,90 @@ +use std::collections::HashSet; + +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtic_syntax::ast::App; + +use crate::{ + check::Extra, + codegen::{schedule_body, util}, +}; + +/// Generates all `${ctxt}::Schedule` methods +pub fn codegen(app: &App, extra: &Extra) -> Vec<TokenStream2> { + let mut items = vec![]; + + let mut seen = HashSet::<_>::new(); + for (scheduler, schedulees) in app.schedule_callers() { + let m = extra.monotonic(); + let instant = quote!(<#m as rtic::Monotonic>::Instant); + + let mut methods = vec![]; + + for name in schedulees { + let schedulee = &app.software_tasks[name]; + let cfgs = &schedulee.cfgs; + let (args, _, untupled, ty) = util::regroup_inputs(&schedulee.inputs); + let args = &args; + + if scheduler.is_init() { + // `init` uses a special `schedule` implementation; it doesn't use the + // `schedule_${name}` functions which are shared by other contexts + + let body = schedule_body::codegen(scheduler, &name, app); + + methods.push(quote!( + #(#cfgs)* + pub fn #name(&self, instant: #instant #(,#args)*) -> Result<(), #ty> { + #body + } + )); + } else { + let schedule = util::schedule_ident(name); + + if !seen.contains(name) { + // Generate a `schedule_${name}_S${sender}` function + seen.insert(name); + + let body = schedule_body::codegen(scheduler, &name, app); + + items.push(quote!( + #(#cfgs)* + pub unsafe fn #schedule( + priority: &rtic::export::Priority, + instant: #instant + #(,#args)* + ) -> Result<(), #ty> { + #body + } + )); + } + + methods.push(quote!( + #(#cfgs)* + #[inline(always)] + pub fn #name(&self, instant: #instant #(,#args)*) -> Result<(), #ty> { + unsafe { + #schedule(self.priority(), instant #(,#untupled)*) + } + } + )); + } + } + + let lt = if scheduler.is_init() { + None + } else { + Some(quote!('a)) + }; + + let scheduler = scheduler.ident(app); + debug_assert!(!methods.is_empty()); + items.push(quote!( + impl<#lt> #scheduler::Schedule<#lt> { + #(#methods)* + } + )); + } + + items +} diff --git a/macros/src/codegen/schedule_body.rs b/macros/src/codegen/schedule_body.rs new file mode 100644 index 00000000..644930d7 --- /dev/null +++ b/macros/src/codegen/schedule_body.rs @@ -0,0 +1,59 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtic_syntax::{ast::App, Context}; +use syn::Ident; + +use crate::codegen::util; + +pub fn codegen(scheduler: Context, name: &Ident, app: &App) -> TokenStream2 { + let schedulee = &app.software_tasks[name]; + + let fq = util::fq_ident(name); + let tq = util::tq_ident(); + let (dequeue, enqueue) = if scheduler.is_init() { + (quote!(#fq.dequeue()), quote!(#tq.enqueue_unchecked(nr);)) + } else { + ( + quote!((#fq { priority }).lock(|fq| fq.split().1.dequeue())), + quote!((#tq { priority }).lock(|tq| tq.enqueue_unchecked(nr));), + ) + }; + + let write_instant = if app.uses_schedule() { + let instants = util::instants_ident(name); + + Some(quote!( + #instants.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(instant); + )) + } else { + None + }; + + let (_, tupled, _, _) = util::regroup_inputs(&schedulee.inputs); + let inputs = util::inputs_ident(name); + let t = util::schedule_t_ident(); + quote!( + unsafe { + use rtic::Mutex as _; + + let input = #tupled; + if let Some(index) = #dequeue { + #inputs.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(input); + + #write_instant + + let nr = rtic::export::NotReady { + instant, + index, + task: #t::#name, + }; + + #enqueue + + Ok(()) + } else { + Err(input) + } + } + ) +} diff --git a/macros/src/codegen/software_tasks.rs b/macros/src/codegen/software_tasks.rs new file mode 100644 index 00000000..4ae37e4e --- /dev/null +++ b/macros/src/codegen/software_tasks.rs @@ -0,0 +1,169 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::{format_ident, quote}; +use rtic_syntax::{ast::App, Context}; + +use crate::{ + analyze::Analysis, + check::Extra, + codegen::{locals, module, resources_struct, util}, +}; + +pub fn codegen( + app: &App, + analysis: &Analysis, + extra: &Extra, +) -> ( + // mod_app_software_tasks -- free queues, buffers and `${task}Resources` constructors + Vec<TokenStream2>, + // root_software_tasks -- items that must be placed in the root of the crate: + // - `${task}Locals` structs + // - `${task}Resources` structs + // - `${task}` modules + Vec<TokenStream2>, + // user_software_tasks -- the `#[task]` functions written by the user + Vec<TokenStream2>, + // user_software_tasks_imports -- the imports for `#[task]` functions written by the user + Vec<TokenStream2>, +) { + let mut mod_app = vec![]; + let mut root = vec![]; + let mut user_tasks = vec![]; + let mut software_tasks_imports = vec![]; + + for (name, task) in &app.software_tasks { + let inputs = &task.inputs; + let (_, _, _, input_ty) = util::regroup_inputs(inputs); + + let cap = task.args.capacity; + let cap_lit = util::capacity_literal(cap); + let cap_ty = util::capacity_typenum(cap, true); + + // Create free queues and inputs / instants buffers + if let Some(&ceiling) = analysis.free_queues.get(name) { + let fq = util::fq_ident(name); + + let (fq_ty, fq_expr, mk_uninit): (_, _, Box<dyn Fn() -> Option<_>>) = { + ( + quote!(rtic::export::SCFQ<#cap_ty>), + quote!(rtic::export::Queue(unsafe { + rtic::export::iQueue::u8_sc() + })), + Box::new(|| util::link_section_uninit(true)), + ) + }; + mod_app.push(quote!( + /// Queue version of a free-list that keeps track of empty slots in + /// the following buffers + static mut #fq: #fq_ty = #fq_expr; + )); + + // Generate a resource proxy if needed + if let Some(ceiling) = ceiling { + mod_app.push(quote!( + struct #fq<'a> { + priority: &'a rtic::export::Priority, + } + )); + + mod_app.push(util::impl_mutex( + extra, + &[], + false, + &fq, + fq_ty, + ceiling, + quote!(&mut #fq), + )); + } + + let ref elems = (0..cap) + .map(|_| quote!(core::mem::MaybeUninit::uninit())) + .collect::<Vec<_>>(); + + if app.uses_schedule() { + let m = extra.monotonic(); + let instants = util::instants_ident(name); + + let uninit = mk_uninit(); + mod_app.push(quote!( + #uninit + /// Buffer that holds the instants associated to the inputs of a task + static mut #instants: + [core::mem::MaybeUninit<<#m as rtic::Monotonic>::Instant>; #cap_lit] = + [#(#elems,)*]; + )); + } + + let uninit = mk_uninit(); + let inputs = util::inputs_ident(name); + mod_app.push(quote!( + #uninit + /// Buffer that holds the inputs of a task + static mut #inputs: [core::mem::MaybeUninit<#input_ty>; #cap_lit] = + [#(#elems,)*]; + )); + } + + // `${task}Resources` + let mut needs_lt = false; + if !task.args.resources.is_empty() { + let (item, constructor) = resources_struct::codegen( + Context::SoftwareTask(name), + task.args.priority, + &mut needs_lt, + app, + analysis, + ); + + // Add resources to imports + let name_res = format_ident!("{}Resources", name); + software_tasks_imports.push(quote!( + #[allow(non_snake_case)] + use super::#name_res; + )); + + root.push(item); + + mod_app.push(constructor); + } + + // `${task}Locals` + let mut locals_pat = None; + if !task.locals.is_empty() { + let (struct_, pat) = locals::codegen(Context::SoftwareTask(name), &task.locals, app); + + locals_pat = Some(pat); + root.push(struct_); + } + + let context = &task.context; + let attrs = &task.attrs; + let cfgs = &task.cfgs; + let stmts = &task.stmts; + let locals_pat = locals_pat.iter(); + user_tasks.push(quote!( + #(#attrs)* + #(#cfgs)* + #[allow(non_snake_case)] + pub fn #name(#(#locals_pat,)* #context: #name::Context #(,#inputs)*) { + use rtic::Mutex as _; + + #(#stmts)* + } + )); + software_tasks_imports.push(quote!( + #(#cfgs)* + #[allow(non_snake_case)] + use super::#name; + )); + + root.push(module::codegen( + Context::SoftwareTask(name), + needs_lt, + app, + extra, + )); + } + + (mod_app, root, user_tasks, software_tasks_imports) +} diff --git a/macros/src/codegen/spawn.rs b/macros/src/codegen/spawn.rs new file mode 100644 index 00000000..da281516 --- /dev/null +++ b/macros/src/codegen/spawn.rs @@ -0,0 +1,121 @@ +use std::collections::HashSet; + +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtic_syntax::ast::App; + +use crate::{ + analyze::Analysis, + check::Extra, + codegen::{spawn_body, util}, +}; + +/// Generates all `${ctxt}::Spawn` methods +pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec<TokenStream2> { + let mut items = vec![]; + + let mut seen = HashSet::<_>::new(); + for (spawner, spawnees) in app.spawn_callers() { + let mut methods = vec![]; + + for name in spawnees { + let spawnee = &app.software_tasks[name]; + let cfgs = &spawnee.cfgs; + let (args, _, untupled, ty) = util::regroup_inputs(&spawnee.inputs); + let args = &args; + + if spawner.is_init() { + // `init` uses a special spawn implementation; it doesn't use the `spawn_${name}` + // functions which are shared by other contexts + + let body = spawn_body::codegen(spawner, &name, app, analysis, extra); + + let let_instant = if app.uses_schedule() { + let m = extra.monotonic(); + + Some(quote!(let instant = unsafe { <#m as rtic::Monotonic>::zero() };)) + } else { + None + }; + + methods.push(quote!( + #(#cfgs)* + pub fn #name(&self #(,#args)*) -> Result<(), #ty> { + #let_instant + #body + } + )); + } else { + let spawn = util::spawn_ident(name); + + if !seen.contains(name) { + // Generate a `spawn_${name}_S${sender}` function + seen.insert(name); + + let instant = if app.uses_schedule() { + let m = extra.monotonic(); + + Some(quote!(, instant: <#m as rtic::Monotonic>::Instant)) + } else { + None + }; + + let body = spawn_body::codegen(spawner, &name, app, analysis, extra); + + items.push(quote!( + #(#cfgs)* + unsafe fn #spawn( + priority: &rtic::export::Priority + #instant + #(,#args)* + ) -> Result<(), #ty> { + #body + } + )); + } + + let (let_instant, instant) = if app.uses_schedule() { + let m = extra.monotonic(); + + ( + Some(if spawner.is_idle() { + quote!(let instant = <#m as rtic::Monotonic>::now();) + } else { + quote!(let instant = self.instant();) + }), + Some(quote!(, instant)), + ) + } else { + (None, None) + }; + + methods.push(quote!( + #(#cfgs)* + #[inline(always)] + pub fn #name(&self #(,#args)*) -> Result<(), #ty> { + unsafe { + #let_instant + #spawn(self.priority() #instant #(,#untupled)*) + } + } + )); + } + } + + let lt = if spawner.is_init() { + None + } else { + Some(quote!('a)) + }; + + let spawner = spawner.ident(app); + debug_assert!(!methods.is_empty()); + items.push(quote!( + impl<#lt> #spawner::Spawn<#lt> { + #(#methods)* + } + )); + } + + items +} diff --git a/macros/src/codegen/spawn_body.rs b/macros/src/codegen/spawn_body.rs new file mode 100644 index 00000000..4ecd0757 --- /dev/null +++ b/macros/src/codegen/spawn_body.rs @@ -0,0 +1,76 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtic_syntax::{ast::App, Context}; +use syn::Ident; + +use crate::{analyze::Analysis, check::Extra, codegen::util}; + +pub fn codegen( + spawner: Context, + name: &Ident, + app: &App, + analysis: &Analysis, + extra: &Extra, +) -> TokenStream2 { + let spawnee = &app.software_tasks[name]; + let priority = spawnee.args.priority; + + let write_instant = if app.uses_schedule() { + let instants = util::instants_ident(name); + + Some(quote!( + #instants.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(instant); + )) + } else { + None + }; + + let t = util::spawn_t_ident(priority); + let fq = util::fq_ident(name); + let rq = util::rq_ident(priority); + let (dequeue, enqueue) = if spawner.is_init() { + ( + quote!(#fq.dequeue()), + quote!(#rq.enqueue_unchecked((#t::#name, index));), + ) + } else { + ( + quote!((#fq { priority }.lock(|fq| fq.split().1.dequeue()))), + quote!((#rq { priority }.lock(|rq| { + rq.split().0.enqueue_unchecked((#t::#name, index)) + }));), + ) + }; + + let device = extra.device; + let enum_ = util::interrupt_ident(); + let interrupt = &analysis.interrupts.get(&priority); + let pend = { + quote!( + rtic::pend(#device::#enum_::#interrupt); + ) + }; + + let (_, tupled, _, _) = util::regroup_inputs(&spawnee.inputs); + let inputs = util::inputs_ident(name); + quote!( + unsafe { + use rtic::Mutex as _; + + let input = #tupled; + if let Some(index) = #dequeue { + #inputs.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(input); + + #write_instant + + #enqueue + + #pend + + Ok(()) + } else { + Err(input) + } + } + ) +} diff --git a/macros/src/codegen/timer_queue.rs b/macros/src/codegen/timer_queue.rs new file mode 100644 index 00000000..030158e2 --- /dev/null +++ b/macros/src/codegen/timer_queue.rs @@ -0,0 +1,137 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtic_syntax::ast::App; + +use crate::{analyze::Analysis, check::Extra, codegen::util}; + +/// Generates timer queues and timer queue handlers +pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec<TokenStream2> { + let mut items = vec![]; + + if let Some(timer_queue) = &analysis.timer_queues.first() { + let t = util::schedule_t_ident(); + + // Enumeration of `schedule`-able tasks + { + let variants = timer_queue + .tasks + .iter() + .map(|name| { + let cfgs = &app.software_tasks[name].cfgs; + + quote!( + #(#cfgs)* + #name + ) + }) + .collect::<Vec<_>>(); + + let doc = format!("Tasks that can be scheduled"); + items.push(quote!( + #[doc = #doc] + #[allow(non_camel_case_types)] + #[derive(Clone, Copy)] + enum #t { + #(#variants,)* + } + )); + } + + let tq = util::tq_ident(); + + // Static variable and resource proxy + { + let doc = format!("Timer queue"); + let m = extra.monotonic(); + let n = util::capacity_typenum(timer_queue.capacity, false); + let tq_ty = quote!(rtic::export::TimerQueue<#m, #t, #n>); + + items.push(quote!( + #[doc = #doc] + static mut #tq: #tq_ty = rtic::export::TimerQueue( + rtic::export::BinaryHeap( + rtic::export::iBinaryHeap::new() + ) + ); + + struct #tq<'a> { + priority: &'a rtic::export::Priority, + } + )); + + items.push(util::impl_mutex( + extra, + &[], + false, + &tq, + tq_ty, + timer_queue.ceiling, + quote!(&mut #tq), + )); + } + + // Timer queue handler + { + let device = extra.device; + let arms = timer_queue + .tasks + .iter() + .map(|name| { + let task = &app.software_tasks[name]; + + let cfgs = &task.cfgs; + let priority = task.args.priority; + let rq = util::rq_ident(priority); + let rqt = util::spawn_t_ident(priority); + let enum_ = util::interrupt_ident(); + let interrupt = &analysis.interrupts.get(&priority); + + let pend = { + quote!( + rtic::pend(#device::#enum_::#interrupt); + ) + }; + + quote!( + #(#cfgs)* + #t::#name => { + (#rq { priority: &rtic::export::Priority::new(PRIORITY) }).lock(|rq| { + rq.split().0.enqueue_unchecked((#rqt::#name, index)) + }); + + #pend + } + ) + }) + .collect::<Vec<_>>(); + + let priority = timer_queue.priority; + let sys_tick = util::suffixed("SysTick"); + items.push(quote!( + #[no_mangle] + unsafe fn #sys_tick() { + use rtic::Mutex as _; + + /// The priority of this handler + const PRIORITY: u8 = #priority; + + rtic::export::run(PRIORITY, || { + while let Some((task, index)) = (#tq { + // NOTE dynamic priority is always the static priority at this point + priority: &rtic::export::Priority::new(PRIORITY), + }) + // NOTE `inline(always)` produces faster and smaller code + .lock(#[inline(always)] + |tq| tq.dequeue()) + { + match task { + #(#arms)* + } + } + }); + } + )); + } + } + items +} diff --git a/macros/src/codegen/util.rs b/macros/src/codegen/util.rs new file mode 100644 index 00000000..2f9f3cce --- /dev/null +++ b/macros/src/codegen/util.rs @@ -0,0 +1,247 @@ +use core::sync::atomic::{AtomicUsize, Ordering}; + +use proc_macro2::{Span, TokenStream as TokenStream2}; +use quote::quote; +use rtic_syntax::{ast::App, Context}; +use syn::{Attribute, Ident, LitInt, PatType}; + +use crate::check::Extra; + +/// Turns `capacity` into an unsuffixed integer literal +pub fn capacity_literal(capacity: u8) -> LitInt { + LitInt::new(&capacity.to_string(), Span::call_site()) +} + +/// Turns `capacity` into a type-level (`typenum`) integer +pub fn capacity_typenum(capacity: u8, round_up_to_power_of_two: bool) -> TokenStream2 { + let capacity = if round_up_to_power_of_two { + capacity.checked_next_power_of_two().expect("UNREACHABLE") + } else { + capacity + }; + + let ident = Ident::new(&format!("U{}", capacity), Span::call_site()); + + quote!(rtic::export::consts::#ident) +} + +/// Identifier for the free queue +pub fn fq_ident(task: &Ident) -> Ident { + Ident::new(&format!("{}_FQ", task.to_string()), Span::call_site()) +} + +/// Generates a `Mutex` implementation +pub fn impl_mutex( + extra: &Extra, + cfgs: &[Attribute], + resources_prefix: bool, + name: &Ident, + ty: TokenStream2, + ceiling: u8, + ptr: TokenStream2, +) -> TokenStream2 { + let (path, priority) = if resources_prefix { + (quote!(resources::#name), quote!(self.priority())) + } else { + (quote!(#name), quote!(self.priority)) + }; + + let device = extra.device; + quote!( + #(#cfgs)* + impl<'a> rtic::Mutex for #path<'a> { + type T = #ty; + + #[inline(always)] + fn lock<R>(&mut self, f: impl FnOnce(&mut #ty) -> R) -> R { + /// Priority ceiling + const CEILING: u8 = #ceiling; + + unsafe { + rtic::export::lock( + #ptr, + #priority, + CEILING, + #device::NVIC_PRIO_BITS, + f, + ) + } + } + } + ) +} + +/// Generates an identifier for the `INPUTS` buffer (`spawn` & `schedule` API) +pub fn inputs_ident(task: &Ident) -> Ident { + Ident::new(&format!("{}_INPUTS", task), Span::call_site()) +} + +/// Generates an identifier for the `INSTANTS` buffer (`schedule` API) +pub fn instants_ident(task: &Ident) -> Ident { + Ident::new(&format!("{}_INSTANTS", task), Span::call_site()) +} + +pub fn interrupt_ident() -> Ident { + let span = Span::call_site(); + Ident::new("Interrupt", span) +} + +/// Whether `name` is an exception with configurable priority +pub fn is_exception(name: &Ident) -> bool { + let s = name.to_string(); + + match &*s { + "MemoryManagement" | "BusFault" | "UsageFault" | "SecureFault" | "SVCall" + | "DebugMonitor" | "PendSV" | "SysTick" => true, + + _ => false, + } +} + +/// Generates a pre-reexport identifier for the "late resources" struct +pub fn late_resources_ident(init: &Ident) -> Ident { + Ident::new( + &format!("{}LateResources", init.to_string()), + Span::call_site(), + ) +} + +fn link_section_index() -> usize { + static INDEX: AtomicUsize = AtomicUsize::new(0); + + INDEX.fetch_add(1, Ordering::Relaxed) +} + +// NOTE `None` means in shared memory +pub fn link_section_uninit(empty_expr: bool) -> Option<TokenStream2> { + let section = if empty_expr { + let index = link_section_index(); + format!(".uninit.rtic{}", index) + } else { + format!(".uninit.rtic{}", link_section_index()) + }; + + Some(quote!(#[link_section = #section])) +} + +/// Generates a pre-reexport identifier for the "locals" struct +pub fn locals_ident(ctxt: Context, app: &App) -> Ident { + let mut s = match ctxt { + Context::Init => app.inits.first().unwrap().name.to_string(), + Context::Idle => app.idles.first().unwrap().name.to_string(), + Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(), + }; + + s.push_str("Locals"); + + Ident::new(&s, Span::call_site()) +} + +// Regroups the inputs of a task +// +// `inputs` could be &[`input: Foo`] OR &[`mut x: i32`, `ref y: i64`] +pub fn regroup_inputs( + inputs: &[PatType], +) -> ( + // args e.g. &[`_0`], &[`_0: i32`, `_1: i64`] + Vec<TokenStream2>, + // tupled e.g. `_0`, `(_0, _1)` + TokenStream2, + // untupled e.g. &[`_0`], &[`_0`, `_1`] + Vec<TokenStream2>, + // ty e.g. `Foo`, `(i32, i64)` + TokenStream2, +) { + if inputs.len() == 1 { + let ty = &inputs[0].ty; + + ( + vec![quote!(_0: #ty)], + quote!(_0), + vec![quote!(_0)], + quote!(#ty), + ) + } else { + let mut args = vec![]; + let mut pats = vec![]; + let mut tys = vec![]; + + for (i, input) in inputs.iter().enumerate() { + let i = Ident::new(&format!("_{}", i), Span::call_site()); + let ty = &input.ty; + + args.push(quote!(#i: #ty)); + + pats.push(quote!(#i)); + + tys.push(quote!(#ty)); + } + + let tupled = { + let pats = pats.clone(); + quote!((#(#pats,)*)) + }; + let ty = quote!((#(#tys,)*)); + (args, tupled, pats, ty) + } +} + +/// Generates a pre-reexport identifier for the "resources" struct +pub fn resources_ident(ctxt: Context, app: &App) -> Ident { + let mut s = match ctxt { + Context::Init => app.inits.first().unwrap().name.to_string(), + Context::Idle => app.idles.first().unwrap().name.to_string(), + Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(), + }; + + s.push_str("Resources"); + + Ident::new(&s, Span::call_site()) +} + +/// Generates an identifier for a ready queue +/// +/// There may be several task dispatchers, one for each priority level. +/// The ready queues are SPSC queues +pub fn rq_ident(priority: u8) -> Ident { + Ident::new(&format!("P{}_RQ", priority), Span::call_site()) +} + +/// Generates an identifier for a "schedule" function +/// +/// The methods of the `Schedule` structs invoke these functions. +pub fn schedule_ident(name: &Ident) -> Ident { + Ident::new(&format!("schedule_{}", name.to_string()), Span::call_site()) +} + +/// Generates an identifier for the `enum` of `schedule`-able tasks +pub fn schedule_t_ident() -> Ident { + Ident::new(&format!("T"), Span::call_site()) +} + +/// Generates an identifier for a "spawn" function +/// +/// The methods of the `Spawn` structs invoke these functions. +pub fn spawn_ident(name: &Ident) -> Ident { + Ident::new(&format!("spawn_{}", name.to_string()), Span::call_site()) +} + +/// Generates an identifier for the `enum` of `spawn`-able tasks +/// +/// This identifier needs the same structure as the `RQ` identifier because there's one ready queue +/// for each of these `T` enums +pub fn spawn_t_ident(priority: u8) -> Ident { + Ident::new(&format!("P{}_T", priority), Span::call_site()) +} + +pub fn suffixed(name: &str) -> Ident { + let span = Span::call_site(); + Ident::new(name, span) +} + +/// Generates an identifier for a timer queue +/// +/// At most there is one timer queue +pub fn tq_ident() -> Ident { + Ident::new(&format!("TQ"), Span::call_site()) +} diff --git a/macros/src/lib.rs b/macros/src/lib.rs index c8d9fee1..e659559e 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -1,123 +1,119 @@ #![deny(warnings)] -#![recursion_limit = "128"] extern crate proc_macro; use proc_macro::TokenStream; -use syn::parse_macro_input; +use std::{fs, path::Path}; + +use rtic_syntax::Settings; mod analyze; mod check; mod codegen; -mod syntax; +#[cfg(test)] +mod tests; -/// Attribute used to declare a RTFM application +/// Attribute used to declare a RTIC application /// -/// This attribute must be applied to a `const` item of type `()`. The `const` item is effectively -/// used as a `mod` item: its value must be a block that contains items commonly found in modules, +/// This attribute must be applied to a module block that contains items commonly found in modules, /// like functions and `static` variables. /// /// The `app` attribute has one mandatory argument: /// /// - `device = <path>`. The path must point to a device crate generated using [`svd2rust`] -/// **v0.14.x**. +/// **v0.14.x** or newer. /// /// [`svd2rust`]: https://crates.io/crates/svd2rust /// -/// The items allowed in the block value of the `const` item are specified below: -/// -/// # 1. `static [mut]` variables -/// -/// These variables are used as *resources*. Resources can be owned by tasks or shared between them. -/// Tasks can get `&mut` (exclusives) references to `static mut` resources, but only `&` (shared) -/// references to `static` resources. Lower priority tasks will need a [`lock`] to get a `&mut` -/// reference to a `static mut` resource shared with higher priority tasks. -/// -/// [`lock`]: ../rtfm/trait.Mutex.html#method.lock +/// and a few optional arguments: /// -/// `static mut` resources that are shared by tasks that run at *different* priorities need to -/// implement the [`Send`] trait. Similarly, `static` resources that are shared by tasks that run at -/// *different* priorities need to implement the [`Sync`] trait. +/// - `peripherals = <bool>`. Indicates whether the runtime takes the device peripherals and makes +/// them available to the `init` context. /// -/// [`Send`]: https://doc.rust-lang.org/core/marker/trait.Send.html -/// [`Sync`]: https://doc.rust-lang.org/core/marker/trait.Sync.html +/// - `monotonic = <path>`. This is a path to a zero-sized structure (e.g. `struct Foo;`) that +/// implements the `Monotonic` trait. This argument must be provided to use the `schedule` API. /// -/// Resources can be initialized at runtime by assigning them `()` (the unit value) as their initial -/// value in their declaration. These "late" resources need to be initialized an the end of the -/// `init` function. +/// The items allowed in the module block are specified below: /// -/// The `app` attribute will inject a `resources` module in the root of the crate. This module -/// contains proxy `struct`s that implement the [`Mutex`] trait. The `struct` are named after the -/// `static mut` resources. For example, `static mut FOO: u32 = 0` will map to a `resources::FOO` -/// `struct` that implements the `Mutex<Data = u32>` trait. +/// # 1. `#[resources] struct <resource-name>` /// -/// [`Mutex`]: ../rtfm/trait.Mutex.html +/// This structure contains the declaration of all the resources used by the application. Each field +/// in this structure corresponds to a different resource. Each resource may optionally be given an +/// initial value using the `#[init(<value>)]` attribute. Resources with no compile-time initial +/// value as referred to as *late* resources. /// /// # 2. `fn` /// -/// Functions must contain *one* of the following attributes: `init`, `idle`, `interrupt`, -/// `exception` or `task`. The attribute defines the role of the function in the application. +/// Functions must contain *one* of the following attributes: `init`, `idle` or `task`. The +/// attribute defines the role of the function in the application. /// /// ## a. `#[init]` /// /// This attribute indicates that the function is to be used as the *initialization function*. There /// must be exactly one instance of the `init` attribute inside the `app` pseudo-module. The -/// signature of the `init` function must be `[unsafe] fn ()`. +/// signature of the `init` function must be `fn (<fn-name>::Context) [-> <fn-name>::LateResources]` +/// where `<fn-name>` is the name of the function adorned with the `#[init]` attribute. /// /// The `init` function runs after memory (RAM) is initialized and runs with interrupts disabled. /// Interrupts are re-enabled after `init` returns. /// /// The `init` attribute accepts the following optional arguments: /// -/// - `resources = [RESOURCE_A, RESOURCE_B, ..]`. This is the list of resources this function has +/// - `resources = [resource_a, resource_b, ..]`. This is the list of resources this context has /// access to. /// -/// - `schedule = [task_a, task_b, ..]`. This is the list of *software* tasks that this function can -/// schedule to run in the future. *IMPORTANT*: This argument is accepted only if the `timer-queue` -/// feature has been enabled. +/// - `schedule = [task_a, task_b, ..]`. This is the list of *software* tasks that this context can +/// schedule to run in the future. *IMPORTANT*: This argument is accepted only if the `monotonic` +/// argument is passed to the `#[app]` attribute. /// -/// - `spawn = [task_a, task_b, ..]`. This is the list of *software* tasks that this function can +/// - `spawn = [task_a, task_b, ..]`. This is the list of *software* tasks that this context can /// immediately spawn. /// -/// The `app` attribute will injected a *context* into this function that comprises the following -/// variables: +/// The first argument of the function, `<fn-name>::Context`, is a structure that contains the +/// following fields: /// -/// - `core: rtfm::Peripherals`. Exclusive access to core peripherals. See [`rtfm::Peripherals`] for -/// more details. +/// - `core`. Exclusive access to core peripherals. The type of this field is [`rtic::Peripherals`] +/// when the `schedule` API is used and [`cortex_m::Peripherals`] when it's not. /// -/// [`rtfm::Peripherals`]: ../rtfm/struct.Peripherals.html +/// [`rtic::Peripherals`]: ../rtic/struct.Peripherals.html +/// [`cortex_m::Peripherals`]: https://docs.rs/cortex-m/0.6/cortex_m/peripheral/struct.Peripherals.html /// -/// - `device: <device-path>::Peripherals`. Exclusive access to device-specific peripherals. -/// `<device-path>` is the path to the device crate declared in the top `app` attribute. +/// - `device: <device>::Peripherals`. Exclusive access to device-specific peripherals. This +/// field is only present when the `peripherals` argument of the `#[app]` attribute is set to +/// `true`. `<device>` is the path to the device crate specified in the top `app` attribute. /// -/// - `start: rtfm::Instant`. The `start` time of the system: `Instant(0 /* cycles */)`. **NOTE**: -/// only present if the `timer-queue` feature is enabled. +/// - `start: <Instant>`. The `start` time of the system: `<Instant>::zero()`. `<Instant>` is the +/// `Instant` type associated to the `Monotonic` implementation specified in the top `#[app]` +/// attribute. **NOTE**: this field is only present when the `schedule` is used. /// -/// - `resources: _`. An opaque `struct` that contains all the resources assigned to this function. -/// The resource maybe appear by value (`impl Singleton`), by references (`&[mut]`) or by proxy -/// (`impl Mutex`). +/// - `resources: <fn-name>::Resources`. A `struct` that contains all the resources that can be +/// accessed from this context. Each field is a different resource; each resource may appear as a +/// reference (`&[mut]-`) or as proxy structure that implements the [`rftm::Mutex`][rtic-mutex] trait. /// -/// - `schedule: init::Schedule`. A `struct` that can be used to schedule *software* tasks. -/// **NOTE**: only present if the `timer-queue` feature is enabled. +/// [rtic-mutex]: ../rtic/trait.Mutex.html /// -/// - `spawn: init::Spawn`. A `struct` that can be used to spawn *software* tasks. +/// - `schedule: <fn-name>::Schedule`. A `struct` that can be used to schedule *software* tasks. /// -/// Other properties / constraints: +/// - `spawn: <fn-name>::Spawn`. A `struct` that can be used to spawn *software* tasks. +/// +/// The return type `<fn-name>::LateResources` must only be specified when late resources, resources +/// with no initial value declared at compile time, are used. `<fn-name>::LateResources` is a +/// structure where each field corresponds to a different late resource. The +/// `<fn-name>::LateResources` value returned by the `#[init]` function is used to initialize the +/// late resources before `idle` or any task can start. /// -/// - The `init` function can **not** be called from software. +/// Other properties: /// /// - The `static mut` variables declared at the beginning of this function will be transformed into /// `&'static mut` references that are safe to access. For example, `static mut FOO: u32 = 0` will /// become `FOO: &'static mut u32`. /// -/// - Assignments (e.g. `FOO = 0`) at the end of this function can be used to initialize *late* -/// resources. -/// /// ## b. `#[idle]` /// /// This attribute indicates that the function is to be used as the *idle task*. There can be at /// most once instance of the `idle` attribute inside the `app` pseudo-module. The signature of the -/// `idle` function must be `fn() -> !`. +/// `idle` function must be `fn(<fn-name>::Context) -> !` where `<fn-name>` is the name of the +/// function adorned with the `#[idle]` attribute. /// /// The `idle` task is a special task that always runs in the background. The `idle` task runs at /// the lowest priority of `0`. If the `idle` task is not defined then the runtime sets the @@ -133,38 +129,37 @@ mod syntax; /// /// - `spawn = (..)`. Same meaning / function as [`#[init].spawn`](#a-init). /// -/// The `app` attribute will injected a *context* into this function that comprises the following -/// variables: -/// -/// - `resources: _`. Same meaning / function as [`init.resources`](#a-init). +/// The first argument of the function, `idle::Context`, is a structure that contains the following +/// fields: /// -/// - `schedule: idle::Schedule`. Same meaning / function as [`init.schedule`](#a-init). +/// - `resources: _`. Same meaning / function as [`<init>::Context.resources`](#a-init). /// -/// - `spawn: idle::Spawn`. Same meaning / function as [`init.spawn`](#a-init). +/// - `schedule: idle::Schedule`. Same meaning / function as [`<init>::Context.schedule`](#a-init). /// -/// Other properties / constraints: +/// - `spawn: idle::Spawn`. Same meaning / function as [`<init>::Context.spawn`](#a-init). /// -/// - The `idle` function can **not** be called from software. +/// Other properties: /// /// - The `static mut` variables declared at the beginning of this function will be transformed into /// `&'static mut` references that are safe to access. For example, `static mut FOO: u32 = 0` will /// become `FOO: &'static mut u32`. /// -/// ## c. `#[exception]` -/// -/// This attribute indicates that the function is to be used as an *exception handler*, a type of -/// hardware task. The signature of `exception` handlers must be `[unsafe] fn()`. +/// ## c. `#[task]` /// -/// The name of the function must match one of the Cortex-M exceptions that has [configurable -/// priority][system-handler]. +/// This attribute indicates that the function is either a hardware task or a software task. The +/// signature of hardware tasks must be `fn(<fn-name>::Context)` whereas the signature of software +/// tasks must be `fn(<fn-name>::Context, <inputs>)`. `<fn-name>` refers to the name of the function +/// adorned with the `#[task]` attribute. /// -/// [system-handler]: ../cortex_m/peripheral/scb/enum.SystemHandler.html +/// The `task` attribute accepts the following optional arguments. /// -/// The `exception` attribute accepts the following optional arguments. +/// - `binds = <interrupt-name>`. Binds this task to a particular interrupt. When this argument is +/// present the task is treated as a hardware task; when it's omitted the task treated is treated as +/// a software task. /// /// - `priority = <integer>`. This is the static priority of the exception handler. The value must /// be in the range `1..=(1 << <device-path>::NVIC_PRIO_BITS)` where `<device-path>` is the path to -/// the device crate declared in the top `app` attribute. If this argument is omitted the priority +/// the device crate specified in the top `app` attribute. If this argument is omitted the priority /// is assumed to be 1. /// /// - `resources = (..)`. Same meaning / function as [`#[init].resources`](#a-init). @@ -173,105 +168,26 @@ mod syntax; /// /// - `spawn = (..)`. Same meaning / function as [`#[init].spawn`](#a-init). /// -/// The `app` attribute will injected a *context* into this function that comprises the following -/// variables: +/// The first argument of the function, `<fn-name>::Context`, is a structure that contains the +/// following fields: /// -/// - `start: rtfm::Instant`. The time at which this handler started executing. **NOTE**: only -/// present if the `timer-queue` feature is enabled. +/// - `start: <Instant>`. For hardware tasks this is the time at which this handler started +/// executing. For software tasks this is the time at which the task was scheduled to run. **NOTE**: +/// only present when the `schedule` API is used. /// -/// - `resources: _`. Same meaning / function as [`init.resources`](#a-init). +/// - `resources: _`. Same meaning / function as [`<init>::Context.resources`](#a-init). /// -/// - `schedule: <exception-name>::Schedule`. Same meaning / function as [`init.schedule`](#a-init). +/// - `schedule: <exception-name>::Schedule`. Same meaning / function as +/// [`<init>::Context.schedule`](#a-init). /// -/// - `spawn: <exception-name>::Spawn`. Same meaning / function as [`init.spawn`](#a-init). +/// - `spawn: <exception-name>::Spawn`. Same meaning / function as +/// [`<init>::Context.spawn`](#a-init). /// /// Other properties / constraints: /// -/// - `exception` handlers can **not** be called from software. -/// /// - The `static mut` variables declared at the beginning of this function will be transformed into -/// `&mut` references that are safe to access. For example, `static mut FOO: u32 = 0` will -/// become `FOO: &mut u32`. -/// -/// ## d. `#[interrupt]` -/// -/// This attribute indicates that the function is to be used as an *interrupt handler*, a type of -/// hardware task. The signature of `interrupt` handlers must be `[unsafe] fn()`. -/// -/// The name of the function must match one of the device specific interrupts. See your device crate -/// documentation (`Interrupt` enum) for more details. -/// -/// The `interrupt` attribute accepts the following optional arguments. -/// -/// - `priority = (..)`. Same meaning / function as [`#[exception].priority`](#b-exception). -/// -/// - `resources = (..)`. Same meaning / function as [`#[init].resources`](#a-init). -/// -/// - `schedule = (..)`. Same meaning / function as [`#[init].schedule`](#a-init). -/// -/// - `spawn = (..)`. Same meaning / function as [`#[init].spawn`](#a-init). -/// -/// The `app` attribute will injected a *context* into this function that comprises the following -/// variables: -/// -/// - `start: rtfm::Instant`. Same meaning / function as [`exception.start`](#b-exception). -/// -/// - `resources: _`. Same meaning / function as [`init.resources`](#a-init). -/// -/// - `schedule: <interrupt-name>::Schedule`. Same meaning / function as [`init.schedule`](#a-init). -/// -/// - `spawn: <interrupt-name>::Spawn`. Same meaning / function as [`init.spawn`](#a-init). -/// -/// Other properties / constraints: -/// -/// - `interrupt` handlers can **not** be called from software, but they can be [`pend`]-ed by the -/// software from any context. -/// -/// [`pend`]: ../rtfm/fn.pend.html -/// -/// - The `static mut` variables declared at the beginning of this function will be transformed into -/// `&mut` references that are safe to access. For example, `static mut FOO: u32 = 0` will -/// become `FOO: &mut u32`. -/// -/// ## e. `#[task]` -/// -/// This attribute indicates that the function is to be used as a *software task*. The signature of -/// software `task`s must be `[unsafe] fn(<inputs>)`. -/// -/// The `task` attribute accepts the following optional arguments. -/// -/// - `capacity = <integer>`. The maximum number of instances of this task that can be queued onto -/// the task scheduler for execution. The value must be in the range `1..=255`. If the `capacity` -/// argument is omitted then the capacity will be inferred. -/// -/// - `priority = <integer>`. Same meaning / function as [`#[exception].priority`](#b-exception). -/// -/// - `resources = (..)`. Same meaning / function as [`#[init].resources`](#a-init). -/// -/// - `schedule = (..)`. Same meaning / function as [`#[init].schedule`](#a-init). -/// -/// - `spawn = (..)`. Same meaning / function as [`#[init].spawn`](#a-init). -/// -/// The `app` attribute will injected a *context* into this function that comprises the following -/// variables: -/// -/// - `scheduled: rtfm::Instant`. The time at which this task was scheduled to run. **NOTE**: Only -/// present if `timer-queue` is enabled. -/// -/// - `resources: _`. Same meaning / function as [`init.resources`](#a-init). -/// -/// - `schedule: <interrupt-name>::Schedule`. Same meaning / function as [`init.schedule`](#a-init). -/// -/// - `spawn: <interrupt-name>::Spawn`. Same meaning / function as [`init.spawn`](#a-init). -/// -/// Other properties / constraints: -/// -/// - Software `task`s can **not** be called from software, but they can be `spawn`-ed and -/// `schedule`-d by the software from any context. -/// -/// - The `static mut` variables declared at the beginning of this function will be transformed into -/// `&mut` references that are safe to access. For example, `static mut FOO: u32 = 0` will -/// become `FOO: &mut u32`. +/// *non*-static `&mut` references that are safe to access. For example, `static mut FOO: u32 = 0` +/// will become `FOO: &mut u32`. /// /// # 3. `extern` block /// @@ -282,27 +198,35 @@ mod syntax; /// This `extern` block must only contain functions with signature `fn ()`. The names of these /// functions must match the names of the target device interrupts. /// -/// Importantly, attributes can be applied to the functions inside this block. These attributes will -/// be forwarded to the interrupt handlers generated by the `app` attribute. +/// Attributes can be applied to the functions inside this block. These attributes will be forwarded +/// to the interrupt handlers generated by the `app` attribute. + #[proc_macro_attribute] pub fn app(args: TokenStream, input: TokenStream) -> TokenStream { - // Parse - let args = parse_macro_input!(args as syntax::AppArgs); - let items = parse_macro_input!(input as syntax::Input).items; + let mut settings = Settings::default(); + settings.optimize_priorities = true; + settings.parse_binds = true; + settings.parse_extern_interrupt = true; + settings.parse_schedule = true; - let app = match syntax::App::parse(items, args) { + let (app, analysis) = match rtic_syntax::parse(args, input, settings) { Err(e) => return e.to_compile_error().into(), - Ok(app) => app, + Ok(x) => x, }; - // Check the specification - if let Err(e) = check::app(&app) { - return e.to_compile_error().into(); - } + let extra = match check::app(&app, &analysis) { + Err(e) => return e.to_compile_error().into(), + Ok(x) => x, + }; - // Ceiling analysis - let analysis = analyze::app(&app); + let analysis = analyze::app(analysis, &app); + + let ts = codegen::app(&app, &analysis, &extra); + + // Try to write the expanded code to disk + if Path::new("target").exists() { + fs::write("target/rtic-expansion.rs", ts.to_string()).ok(); + } - // Code generation - codegen::app(&app, &analysis).into() + ts.into() } diff --git a/macros/src/syntax.rs b/macros/src/syntax.rs deleted file mode 100644 index 228d9588..00000000 --- a/macros/src/syntax.rs +++ /dev/null @@ -1,1394 +0,0 @@ -use std::{ - collections::{BTreeMap, BTreeSet}, - iter, u8, -}; - -use proc_macro2::Span; -use syn::{ - braced, bracketed, parenthesized, - parse::{self, Parse, ParseStream}, - punctuated::Punctuated, - spanned::Spanned, - token::Brace, - ArgCaptured, AttrStyle, Attribute, Expr, FnArg, ForeignItem, Ident, IntSuffix, Item, ItemFn, - ItemForeignMod, ItemStatic, LitInt, Path, PathArguments, PathSegment, ReturnType, Stmt, Token, - Type, TypeTuple, Visibility, -}; - -pub struct AppArgs { - pub device: Path, -} - -impl Parse for AppArgs { - fn parse(input: ParseStream<'_>) -> parse::Result<Self> { - let mut device = None; - loop { - if input.is_empty() { - break; - } - - // #ident = .. - let ident: Ident = input.parse()?; - let _eq_token: Token![=] = input.parse()?; - - let ident_s = ident.to_string(); - match &*ident_s { - "device" => { - if device.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - device = Some(input.parse()?); - } - _ => { - return Err(parse::Error::new( - ident.span(), - "expected `device`; other keys are not accepted", - )); - } - } - - if input.is_empty() { - break; - } - - // , - let _: Token![,] = input.parse()?; - } - - Ok(AppArgs { - device: device.ok_or(parse::Error::new( - Span::call_site(), - "`device` argument is required", - ))?, - }) - } -} - -pub struct Input { - _const_token: Token![const], - _ident: Ident, - _colon_token: Token![:], - _ty: TypeTuple, - _eq_token: Token![=], - _brace_token: Brace, - pub items: Vec<Item>, - _semi_token: Token![;], -} - -impl Parse for Input { - fn parse(input: ParseStream<'_>) -> parse::Result<Self> { - fn parse_items(input: ParseStream<'_>) -> parse::Result<Vec<Item>> { - let mut items = vec![]; - - while !input.is_empty() { - items.push(input.parse()?); - } - - Ok(items) - } - - let content; - Ok(Input { - _const_token: input.parse()?, - _ident: input.parse()?, - _colon_token: input.parse()?, - _ty: input.parse()?, - _eq_token: input.parse()?, - _brace_token: braced!(content in input), - items: content.call(parse_items)?, - _semi_token: input.parse()?, - }) - } -} - -pub struct App { - pub args: AppArgs, - pub idle: Option<Idle>, - pub init: Init, - pub exceptions: Exceptions, - pub interrupts: Interrupts, - pub resources: Resources, - pub tasks: Tasks, - pub free_interrupts: FreeInterrupts, -} - -impl App { - pub fn parse(items: Vec<Item>, args: AppArgs) -> parse::Result<Self> { - let mut idle = None; - let mut init = None; - let mut exceptions = BTreeMap::new(); - let mut interrupts = BTreeMap::new(); - let mut resources = BTreeMap::new(); - let mut tasks = BTreeMap::new(); - let mut free_interrupts = None; - - for item in items { - match item { - Item::Fn(mut item) => { - if let Some(pos) = item.attrs.iter().position(|attr| eq(attr, "idle")) { - if idle.is_some() { - return Err(parse::Error::new( - item.span(), - "`#[idle]` function must appear at most once", - )); - } - - let args = syn::parse2(item.attrs.swap_remove(pos).tts)?; - - idle = Some(Idle::check(args, item)?); - } else if let Some(pos) = item.attrs.iter().position(|attr| eq(attr, "init")) { - if init.is_some() { - return Err(parse::Error::new( - item.span(), - "`#[init]` function must appear exactly once", - )); - } - - let args = syn::parse2(item.attrs.swap_remove(pos).tts)?; - - init = Some(Init::check(args, item)?); - } else if let Some(pos) = - item.attrs.iter().position(|attr| eq(attr, "exception")) - { - if exceptions.contains_key(&item.ident) - || interrupts.contains_key(&item.ident) - || tasks.contains_key(&item.ident) - { - return Err(parse::Error::new( - item.ident.span(), - "this task is defined multiple times", - )); - } - - let args = syn::parse2(item.attrs.swap_remove(pos).tts)?; - - exceptions.insert(item.ident.clone(), Exception::check(args, item)?); - } else if let Some(pos) = - item.attrs.iter().position(|attr| eq(attr, "interrupt")) - { - if exceptions.contains_key(&item.ident) - || interrupts.contains_key(&item.ident) - || tasks.contains_key(&item.ident) - { - return Err(parse::Error::new( - item.ident.span(), - "this task is defined multiple times", - )); - } - - let args = syn::parse2(item.attrs.swap_remove(pos).tts)?; - - interrupts.insert(item.ident.clone(), Interrupt::check(args, item)?); - } else if let Some(pos) = item.attrs.iter().position(|attr| eq(attr, "task")) { - if exceptions.contains_key(&item.ident) - || interrupts.contains_key(&item.ident) - || tasks.contains_key(&item.ident) - { - return Err(parse::Error::new( - item.ident.span(), - "this task is defined multiple times", - )); - } - - let args = syn::parse2(item.attrs.swap_remove(pos).tts)?; - - tasks.insert(item.ident.clone(), Task::check(args, item)?); - } else { - return Err(parse::Error::new( - item.span(), - "this item must live outside the `#[app]` module", - )); - } - } - Item::Static(item) => { - if resources.contains_key(&item.ident) { - return Err(parse::Error::new( - item.ident.span(), - "this resource is listed twice", - )); - } - - resources.insert(item.ident.clone(), Resource::check(item)?); - } - Item::ForeignMod(item) => { - if free_interrupts.is_some() { - return Err(parse::Error::new( - item.abi.extern_token.span(), - "`extern` block can only appear at most once", - )); - } - - free_interrupts = Some(FreeInterrupt::parse(item)?); - } - _ => { - return Err(parse::Error::new( - item.span(), - "this item must live outside the `#[app]` module", - )); - } - } - } - - Ok(App { - args, - idle, - init: init.ok_or_else(|| { - parse::Error::new(Span::call_site(), "`#[init]` function is missing") - })?, - exceptions, - interrupts, - resources, - tasks, - free_interrupts: free_interrupts.unwrap_or_else(|| FreeInterrupts::new()), - }) - } - - /// Returns an iterator over all resource accesses. - /// - /// Each resource access include the priority it's accessed at (`u8`) and the name of the - /// resource (`Ident`). A resource may appear more than once in this iterator - pub fn resource_accesses(&self) -> impl Iterator<Item = (u8, &Ident)> { - self.idle - .as_ref() - .map(|idle| -> Box<dyn Iterator<Item = _>> { - Box::new(idle.args.resources.iter().map(|res| (0, res))) - }) - .unwrap_or_else(|| Box::new(iter::empty())) - .chain(self.exceptions.values().flat_map(|e| { - e.args - .resources - .iter() - .map(move |res| (e.args.priority, res)) - })) - .chain(self.interrupts.values().flat_map(|i| { - i.args - .resources - .iter() - .map(move |res| (i.args.priority, res)) - })) - .chain(self.tasks.values().flat_map(|t| { - t.args - .resources - .iter() - .map(move |res| (t.args.priority, res)) - })) - } - - /// Returns an iterator over all `spawn` calls - /// - /// Each spawn call includes the priority of the task from which it's issued and the name of the - /// task that's spawned. A task may appear more that once in this iterator. - /// - /// A priority of `None` means that this being called from `init` - pub fn spawn_calls(&self) -> impl Iterator<Item = (Option<u8>, &Ident)> { - self.init - .args - .spawn - .iter() - .map(|s| (None, s)) - .chain( - self.idle - .as_ref() - .map(|idle| -> Box<dyn Iterator<Item = _>> { - Box::new(idle.args.spawn.iter().map(|s| (Some(0), s))) - }) - .unwrap_or_else(|| Box::new(iter::empty())), - ) - .chain( - self.exceptions - .values() - .flat_map(|e| e.args.spawn.iter().map(move |s| (Some(e.args.priority), s))), - ) - .chain( - self.interrupts - .values() - .flat_map(|i| i.args.spawn.iter().map(move |s| (Some(i.args.priority), s))), - ) - .chain( - self.tasks - .values() - .flat_map(|t| t.args.spawn.iter().map(move |s| (Some(t.args.priority), s))), - ) - } - - /// Returns an iterator over all `schedule` calls - /// - /// Each spawn call includes the priority of the task from which it's issued and the name of the - /// task that's spawned. A task may appear more that once in this iterator. - #[allow(dead_code)] - pub fn schedule_calls(&self) -> impl Iterator<Item = (Option<u8>, &Ident)> { - self.init - .args - .schedule - .iter() - .map(|s| (None, s)) - .chain( - self.idle - .as_ref() - .map(|idle| -> Box<dyn Iterator<Item = _>> { - Box::new(idle.args.schedule.iter().map(|s| (Some(0), s))) - }) - .unwrap_or_else(|| Box::new(iter::empty())), - ) - .chain(self.exceptions.values().flat_map(|e| { - e.args - .schedule - .iter() - .map(move |s| (Some(e.args.priority), s)) - })) - .chain(self.interrupts.values().flat_map(|i| { - i.args - .schedule - .iter() - .map(move |s| (Some(i.args.priority), s)) - })) - .chain(self.tasks.values().flat_map(|t| { - t.args - .schedule - .iter() - .map(move |s| (Some(t.args.priority), s)) - })) - } - - #[allow(dead_code)] - pub fn schedule_callers(&self) -> impl Iterator<Item = (Ident, &Idents)> { - self.idle - .as_ref() - .map(|idle| -> Box<dyn Iterator<Item = _>> { - Box::new(iter::once(( - Ident::new("idle", Span::call_site()), - &idle.args.schedule, - ))) - }) - .unwrap_or_else(|| Box::new(iter::empty())) - .chain(iter::once(( - Ident::new("init", Span::call_site()), - &self.init.args.schedule, - ))) - .chain( - self.exceptions - .iter() - .map(|(name, exception)| (name.clone(), &exception.args.schedule)), - ) - .chain( - self.interrupts - .iter() - .map(|(name, interrupt)| (name.clone(), &interrupt.args.schedule)), - ) - .chain( - self.tasks - .iter() - .map(|(name, task)| (name.clone(), &task.args.schedule)), - ) - } - - pub fn spawn_callers(&self) -> impl Iterator<Item = (Ident, &Idents)> { - self.idle - .as_ref() - .map(|idle| -> Box<dyn Iterator<Item = _>> { - Box::new(iter::once(( - Ident::new("idle", Span::call_site()), - &idle.args.spawn, - ))) - }) - .unwrap_or_else(|| Box::new(iter::empty())) - .chain(iter::once(( - Ident::new("init", Span::call_site()), - &self.init.args.spawn, - ))) - .chain( - self.exceptions - .iter() - .map(|(name, exception)| (name.clone(), &exception.args.spawn)), - ) - .chain( - self.interrupts - .iter() - .map(|(name, interrupt)| (name.clone(), &interrupt.args.spawn)), - ) - .chain( - self.tasks - .iter() - .map(|(name, task)| (name.clone(), &task.args.spawn)), - ) - } -} - -pub type Idents = BTreeSet<Ident>; - -pub type Exceptions = BTreeMap<Ident, Exception>; - -pub type Interrupts = BTreeMap<Ident, Interrupt>; - -pub type Resources = BTreeMap<Ident, Resource>; - -pub type Statics = Vec<ItemStatic>; - -pub type Tasks = BTreeMap<Ident, Task>; - -pub type FreeInterrupts = BTreeMap<Ident, FreeInterrupt>; - -pub struct Idle { - pub args: IdleArgs, - pub attrs: Vec<Attribute>, - pub unsafety: Option<Token![unsafe]>, - pub statics: BTreeMap<Ident, Static>, - pub stmts: Vec<Stmt>, -} - -pub type IdleArgs = InitArgs; - -impl Idle { - fn check(args: IdleArgs, item: ItemFn) -> parse::Result<Self> { - let valid_signature = item.vis == Visibility::Inherited - && item.constness.is_none() - && item.asyncness.is_none() - && item.abi.is_none() - && item.decl.generics.params.is_empty() - && item.decl.generics.where_clause.is_none() - && item.decl.inputs.is_empty() - && item.decl.variadic.is_none() - && is_bottom(&item.decl.output); - - let span = item.span(); - - if !valid_signature { - return Err(parse::Error::new( - span, - "`idle` must have type signature `[unsafe] fn() -> !`", - )); - } - - let (statics, stmts) = extract_statics(item.block.stmts); - - Ok(Idle { - args, - attrs: item.attrs, - unsafety: item.unsafety, - statics: Static::parse(statics)?, - stmts, - }) - } -} - -pub struct InitArgs { - pub resources: Idents, - pub schedule: Idents, - pub spawn: Idents, -} - -impl Default for InitArgs { - fn default() -> Self { - InitArgs { - resources: Idents::new(), - schedule: Idents::new(), - spawn: Idents::new(), - } - } -} - -impl Parse for InitArgs { - fn parse(input: ParseStream<'_>) -> parse::Result<InitArgs> { - if input.is_empty() { - return Ok(InitArgs::default()); - } - - let mut resources = None; - let mut schedule = None; - let mut spawn = None; - - let content; - parenthesized!(content in input); - loop { - if content.is_empty() { - break; - } - - // #ident = .. - let ident: Ident = content.parse()?; - let _: Token![=] = content.parse()?; - - let ident_s = ident.to_string(); - match &*ident_s { - "schedule" if cfg!(not(feature = "timer-queue")) => { - return Err(parse::Error::new( - ident.span(), - "The `schedule` API requires that the `timer-queue` feature is \ - enabled in the `cortex-m-rtfm` crate", - )); - } - "resources" | "schedule" | "spawn" => {} // OK - _ => { - return Err(parse::Error::new( - ident.span(), - "expected one of: resources, schedule or spawn", - )); - } - } - - // .. [#(#idents)*] - let inner; - bracketed!(inner in content); - let mut idents = Idents::new(); - for ident in inner.call(Punctuated::<_, Token![,]>::parse_terminated)? { - if idents.contains(&ident) { - return Err(parse::Error::new( - ident.span(), - "element appears more than once in list", - )); - } - - idents.insert(ident); - } - - let ident_s = ident.to_string(); - match &*ident_s { - "resources" => { - if resources.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - resources = Some(idents); - } - "schedule" => { - if schedule.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - schedule = Some(idents); - } - "spawn" => { - if spawn.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - spawn = Some(idents); - } - _ => unreachable!(), - } - - if content.is_empty() { - break; - } - - // , - let _: Token![,] = content.parse()?; - } - - Ok(InitArgs { - resources: resources.unwrap_or(Idents::new()), - schedule: schedule.unwrap_or(Idents::new()), - spawn: spawn.unwrap_or(Idents::new()), - }) - } -} - -// TODO remove in v0.5.x -pub struct Assign { - pub attrs: Vec<Attribute>, - pub left: Ident, - pub right: Box<Expr>, -} - -pub struct Init { - pub args: InitArgs, - pub attrs: Vec<Attribute>, - pub unsafety: Option<Token![unsafe]>, - pub statics: BTreeMap<Ident, Static>, - pub stmts: Vec<Stmt>, - // TODO remove in v0.5.x - pub assigns: Vec<Assign>, - pub returns_late_resources: bool, -} - -impl Init { - fn check(args: InitArgs, item: ItemFn) -> parse::Result<Self> { - let mut valid_signature = item.vis == Visibility::Inherited - && item.constness.is_none() - && item.asyncness.is_none() - && item.abi.is_none() - && item.decl.generics.params.is_empty() - && item.decl.generics.where_clause.is_none() - && item.decl.inputs.is_empty() - && item.decl.variadic.is_none(); - - let returns_late_resources = match &item.decl.output { - ReturnType::Default => false, - ReturnType::Type(_, ty) => { - match &**ty { - Type::Tuple(t) => { - if t.elems.is_empty() { - // -> () - true - } else { - valid_signature = false; - - false // don't care - } - } - - Type::Path(p) => { - let mut segments = p.path.segments.iter(); - if p.qself.is_none() - && p.path.leading_colon.is_none() - && p.path.segments.len() == 2 - && segments.next().map(|s| { - s.arguments == PathArguments::None && s.ident.to_string() == "init" - }) == Some(true) - && segments.next().map(|s| { - s.arguments == PathArguments::None - && s.ident.to_string() == "LateResources" - }) == Some(true) - { - // -> init::LateResources - true - } else { - valid_signature = false; - - false // don't care - } - } - - _ => { - valid_signature = false; - - false // don't care - } - } - } - }; - - let span = item.span(); - - if !valid_signature { - return Err(parse::Error::new( - span, - "`init` must have type signature `[unsafe] fn() [-> init::LateResources]`", - )); - } - - let (statics, stmts) = extract_statics(item.block.stmts); - let (stmts, assigns) = if returns_late_resources { - (stmts, vec![]) - } else { - extract_assignments(stmts) - }; - - Ok(Init { - args, - attrs: item.attrs, - unsafety: item.unsafety, - statics: Static::parse(statics)?, - stmts, - assigns, - returns_late_resources, - }) - } -} - -/// Union of `TaskArgs`, `ExceptionArgs` and `InterruptArgs` -pub struct Args { - pub binds: Option<Ident>, - pub capacity: Option<u8>, - pub priority: u8, - pub resources: Idents, - pub schedule: Idents, - pub spawn: Idents, -} - -impl Default for Args { - fn default() -> Self { - Args { - binds: None, - capacity: None, - priority: 1, - resources: Idents::new(), - schedule: Idents::new(), - spawn: Idents::new(), - } - } -} - -pub struct Exception { - pub args: ExceptionArgs, - pub attrs: Vec<Attribute>, - pub unsafety: Option<Token![unsafe]>, - pub statics: BTreeMap<Ident, Static>, - pub stmts: Vec<Stmt>, -} - -pub struct ExceptionArgs { - binds: Option<Ident>, - pub priority: u8, - pub resources: Idents, - pub schedule: Idents, - pub spawn: Idents, -} - -impl ExceptionArgs { - /// Returns the name of the exception / interrupt this handler binds to - pub fn binds<'a>(&'a self, handler: &'a Ident) -> &'a Ident { - self.binds.as_ref().unwrap_or(handler) - } -} - -impl Parse for ExceptionArgs { - fn parse(input: ParseStream<'_>) -> parse::Result<Self> { - parse_args(input, /* binds */ true, /* capacity */ false).map( - |Args { - binds, - priority, - resources, - schedule, - spawn, - .. - }| { - ExceptionArgs { - binds, - priority, - resources, - schedule, - spawn, - } - }, - ) - } -} - -impl Exception { - fn check(args: ExceptionArgs, item: ItemFn) -> parse::Result<Self> { - let valid_signature = item.vis == Visibility::Inherited - && item.constness.is_none() - && item.asyncness.is_none() - && item.abi.is_none() - && item.decl.generics.params.is_empty() - && item.decl.generics.where_clause.is_none() - && item.decl.inputs.is_empty() - && item.decl.variadic.is_none() - && is_unit(&item.decl.output); - - if !valid_signature { - return Err(parse::Error::new( - item.span(), - "`exception` handlers must have type signature `[unsafe] fn()`", - )); - } - - let span = item.ident.span(); - match &*args.binds.as_ref().unwrap_or(&item.ident).to_string() { - "MemoryManagement" | "BusFault" | "UsageFault" | "SecureFault" | "SVCall" - | "DebugMonitor" | "PendSV" => {} // OK - "SysTick" => { - if cfg!(feature = "timer-queue") { - return Err(parse::Error::new( - span, - "the `SysTick` exception can't be used because it's used by \ - the runtime when the `timer-queue` feature is enabled", - )); - } - } - _ => { - return Err(parse::Error::new( - span, - "only exceptions with configurable priority can be used as hardware tasks", - )); - } - } - - let (statics, stmts) = extract_statics(item.block.stmts); - - Ok(Exception { - args, - attrs: item.attrs, - unsafety: item.unsafety, - statics: Static::parse(statics)?, - stmts, - }) - } -} - -pub struct Interrupt { - pub args: InterruptArgs, - pub attrs: Vec<Attribute>, - pub unsafety: Option<Token![unsafe]>, - pub statics: BTreeMap<Ident, Static>, - pub stmts: Vec<Stmt>, -} - -pub type InterruptArgs = ExceptionArgs; - -impl Interrupt { - fn check(args: InterruptArgs, item: ItemFn) -> parse::Result<Self> { - let valid_signature = item.vis == Visibility::Inherited - && item.constness.is_none() - && item.asyncness.is_none() - && item.abi.is_none() - && item.decl.generics.params.is_empty() - && item.decl.generics.where_clause.is_none() - && item.decl.inputs.is_empty() - && item.decl.variadic.is_none() - && is_unit(&item.decl.output); - - let span = item.span(); - - if !valid_signature { - return Err(parse::Error::new( - span, - "`interrupt` handlers must have type signature `[unsafe] fn()`", - )); - } - - match &*item.ident.to_string() { - "init" | "idle" | "resources" => { - return Err(parse::Error::new( - span, - "`interrupt` handlers can NOT be named `idle`, `init` or `resources`", - )); - } - _ => {} - } - - let (statics, stmts) = extract_statics(item.block.stmts); - - Ok(Interrupt { - args, - attrs: item.attrs, - unsafety: item.unsafety, - statics: Static::parse(statics)?, - stmts, - }) - } -} - -pub struct Resource { - pub singleton: bool, - pub cfgs: Vec<Attribute>, - pub attrs: Vec<Attribute>, - pub mutability: Option<Token![mut]>, - pub ty: Box<Type>, - pub expr: Option<Box<Expr>>, -} - -impl Resource { - fn check(mut item: ItemStatic) -> parse::Result<Resource> { - if item.vis != Visibility::Inherited { - return Err(parse::Error::new( - item.span(), - "resources must have inherited / private visibility", - )); - } - - let uninitialized = match *item.expr { - Expr::Tuple(ref tuple) => tuple.elems.is_empty(), - _ => false, - }; - - let pos = item.attrs.iter().position(|attr| eq(attr, "Singleton")); - - if let Some(pos) = pos { - item.attrs[pos].path.segments.insert( - 0, - PathSegment::from(Ident::new("owned_singleton", Span::call_site())), - ); - } - - let (cfgs, attrs) = extract_cfgs(item.attrs); - - Ok(Resource { - singleton: pos.is_some(), - cfgs, - attrs, - mutability: item.mutability, - ty: item.ty, - expr: if uninitialized { None } else { Some(item.expr) }, - }) - } -} - -pub struct TaskArgs { - pub capacity: Option<u8>, - pub priority: u8, - pub resources: Idents, - pub spawn: Idents, - pub schedule: Idents, -} - -impl Parse for TaskArgs { - fn parse(input: ParseStream<'_>) -> parse::Result<Self> { - parse_args(input, /* binds */ false, /* capacity */ true).map( - |Args { - capacity, - priority, - resources, - schedule, - spawn, - .. - }| { - TaskArgs { - capacity, - priority, - resources, - schedule, - spawn, - } - }, - ) - } -} - -// Parser shared by ExceptionArgs, InterruptArgs and TaskArgs -fn parse_args( - input: ParseStream<'_>, - accepts_binds: bool, - accepts_capacity: bool, -) -> parse::Result<Args> { - if input.is_empty() { - return Ok(Args::default()); - } - - let mut binds = None; - let mut capacity = None; - let mut priority = None; - let mut resources = None; - let mut schedule = None; - let mut spawn = None; - - let content; - parenthesized!(content in input); - loop { - if content.is_empty() { - break; - } - - // #ident = .. - let ident: Ident = content.parse()?; - let _: Token![=] = content.parse()?; - - let ident_s = ident.to_string(); - match &*ident_s { - "binds" if accepts_binds => { - if binds.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - // #ident - let ident = content.parse()?; - - binds = Some(ident); - } - "capacity" if accepts_capacity => { - if capacity.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - // #lit - let lit: LitInt = content.parse()?; - - if lit.suffix() != IntSuffix::None { - return Err(parse::Error::new( - lit.span(), - "this literal must be unsuffixed", - )); - } - - let value = lit.value(); - if value > u64::from(u8::MAX) || value == 0 { - return Err(parse::Error::new( - lit.span(), - "this literal must be in the range 1...255", - )); - } - - capacity = Some(value as u8); - } - "priority" => { - if priority.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - // #lit - let lit: LitInt = content.parse()?; - - if lit.suffix() != IntSuffix::None { - return Err(parse::Error::new( - lit.span(), - "this literal must be unsuffixed", - )); - } - - let value = lit.value(); - if value > u64::from(u8::MAX) || value == 0 { - return Err(parse::Error::new( - lit.span(), - "this literal must be in the range 1...255", - )); - } - - priority = Some(value as u8); - } - "schedule" if cfg!(not(feature = "timer-queue")) => { - return Err(parse::Error::new( - ident.span(), - "The `schedule` API requires that the `timer-queue` feature is \ - enabled in the `cortex-m-rtfm` crate", - )); - } - "resources" | "schedule" | "spawn" => { - // .. [#(#idents)*] - let inner; - bracketed!(inner in content); - let mut idents = Idents::new(); - for ident in inner.call(Punctuated::<_, Token![,]>::parse_terminated)? { - if idents.contains(&ident) { - return Err(parse::Error::new( - ident.span(), - "element appears more than once in list", - )); - } - - idents.insert(ident); - } - - match &*ident_s { - "resources" => { - if resources.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - resources = Some(idents); - } - "schedule" => { - if schedule.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - schedule = Some(idents); - } - "spawn" => { - if spawn.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - spawn = Some(idents); - } - _ => unreachable!(), - } - } - _ => { - return Err(parse::Error::new( - ident.span(), - format!( - "expected one of: {}{}priority, resources, schedule or spawn", - if accepts_binds { "binds, " } else { "" }, - if accepts_capacity { "capacity, " } else { "" }, - ), - )); - } - } - - if content.is_empty() { - break; - } - - // , - let _: Token![,] = content.parse()?; - } - - Ok(Args { - binds, - capacity, - priority: priority.unwrap_or(1), - resources: resources.unwrap_or(Idents::new()), - schedule: schedule.unwrap_or(Idents::new()), - spawn: spawn.unwrap_or(Idents::new()), - }) -} - -pub struct Static { - /// `#[cfg]` attributes - pub cfgs: Vec<Attribute>, - /// Attributes that are not `#[cfg]` - pub attrs: Vec<Attribute>, - pub ty: Box<Type>, - pub expr: Box<Expr>, -} - -impl Static { - fn parse(items: Vec<ItemStatic>) -> parse::Result<BTreeMap<Ident, Static>> { - let mut statics = BTreeMap::new(); - - for item in items { - if statics.contains_key(&item.ident) { - return Err(parse::Error::new( - item.ident.span(), - "this `static` is listed twice", - )); - } - - let (cfgs, attrs) = extract_cfgs(item.attrs); - - statics.insert( - item.ident, - Static { - cfgs, - attrs, - ty: item.ty, - expr: item.expr, - }, - ); - } - - Ok(statics) - } -} - -pub struct Task { - pub args: TaskArgs, - pub cfgs: Vec<Attribute>, - pub attrs: Vec<Attribute>, - pub unsafety: Option<Token![unsafe]>, - pub inputs: Vec<ArgCaptured>, - pub statics: BTreeMap<Ident, Static>, - pub stmts: Vec<Stmt>, -} - -impl Task { - fn check(args: TaskArgs, item: ItemFn) -> parse::Result<Self> { - let valid_signature = item.vis == Visibility::Inherited - && item.constness.is_none() - && item.asyncness.is_none() - && item.abi.is_none() - && item.decl.generics.params.is_empty() - && item.decl.generics.where_clause.is_none() - && item.decl.variadic.is_none() - && is_unit(&item.decl.output); - - let span = item.span(); - - if !valid_signature { - return Err(parse::Error::new( - span, - "`task` handlers must have type signature `[unsafe] fn(..)`", - )); - } - - let (statics, stmts) = extract_statics(item.block.stmts); - - let mut inputs = vec![]; - for input in item.decl.inputs { - if let FnArg::Captured(capture) = input { - inputs.push(capture); - } else { - return Err(parse::Error::new( - span, - "inputs must be named arguments (e.f. `foo: u32`) and not include `self`", - )); - } - } - - match &*item.ident.to_string() { - "init" | "idle" | "resources" => { - return Err(parse::Error::new( - span, - "`task` handlers can NOT be named `idle`, `init` or `resources`", - )); - } - _ => {} - } - - let (cfgs, attrs) = extract_cfgs(item.attrs); - Ok(Task { - args, - cfgs, - attrs, - unsafety: item.unsafety, - inputs, - statics: Static::parse(statics)?, - stmts, - }) - } -} - -pub struct FreeInterrupt { - pub attrs: Vec<Attribute>, -} - -impl FreeInterrupt { - fn parse(mod_: ItemForeignMod) -> parse::Result<FreeInterrupts> { - let mut free_interrupts = FreeInterrupts::new(); - - for item in mod_.items { - if let ForeignItem::Fn(f) = item { - let valid_signature = f.vis == Visibility::Inherited - && f.decl.generics.params.is_empty() - && f.decl.generics.where_clause.is_none() - && f.decl.inputs.is_empty() - && f.decl.variadic.is_none() - && is_unit(&f.decl.output); - - if !valid_signature { - return Err(parse::Error::new( - f.span(), - "free interrupts must have type signature `fn()`", - )); - } - - if free_interrupts.contains_key(&f.ident) { - return Err(parse::Error::new( - f.ident.span(), - "this interrupt appears twice", - )); - } - - free_interrupts.insert(f.ident, FreeInterrupt { attrs: f.attrs }); - } else { - return Err(parse::Error::new( - mod_.abi.extern_token.span(), - "`extern` block should only contains functions", - )); - } - } - - Ok(free_interrupts) - } -} - -fn eq(attr: &Attribute, name: &str) -> bool { - attr.style == AttrStyle::Outer && attr.path.segments.len() == 1 && { - let pair = attr.path.segments.first().unwrap(); - let segment = pair.value(); - segment.arguments == PathArguments::None && segment.ident.to_string() == name - } -} - -fn extract_cfgs(attrs: Vec<Attribute>) -> (Vec<Attribute>, Vec<Attribute>) { - let mut cfgs = vec![]; - let mut not_cfgs = vec![]; - - for attr in attrs { - if eq(&attr, "cfg") { - cfgs.push(attr); - } else { - not_cfgs.push(attr); - } - } - - (cfgs, not_cfgs) -} - -/// Extracts `static mut` vars from the beginning of the given statements -fn extract_statics(stmts: Vec<Stmt>) -> (Statics, Vec<Stmt>) { - let mut istmts = stmts.into_iter(); - - let mut statics = Statics::new(); - let mut stmts = vec![]; - while let Some(stmt) = istmts.next() { - match stmt { - Stmt::Item(Item::Static(var)) => { - if var.mutability.is_some() { - statics.push(var); - } else { - stmts.push(Stmt::Item(Item::Static(var))); - break; - } - } - _ => { - stmts.push(stmt); - break; - } - } - } - - stmts.extend(istmts); - - (statics, stmts) -} - -// TODO remove in v0.5.x -fn extract_assignments(stmts: Vec<Stmt>) -> (Vec<Stmt>, Vec<Assign>) { - let mut istmts = stmts.into_iter().rev(); - - let mut assigns = vec![]; - let mut stmts = vec![]; - while let Some(stmt) = istmts.next() { - match stmt { - Stmt::Semi(Expr::Assign(assign), semi) => { - if let Expr::Path(ref expr) = *assign.left { - if expr.path.segments.len() == 1 { - assigns.push(Assign { - attrs: assign.attrs, - left: expr.path.segments[0].ident.clone(), - right: assign.right, - }); - continue; - } - } - - stmts.push(Stmt::Semi(Expr::Assign(assign), semi)); - } - _ => { - stmts.push(stmt); - break; - } - } - } - - stmts.extend(istmts); - - (stmts.into_iter().rev().collect(), assigns) -} - -fn is_bottom(ty: &ReturnType) -> bool { - if let ReturnType::Type(_, ty) = ty { - if let Type::Never(_) = **ty { - true - } else { - false - } - } else { - false - } -} - -fn is_unit(ty: &ReturnType) -> bool { - if let ReturnType::Type(_, ty) = ty { - if let Type::Tuple(ref tuple) = **ty { - tuple.elems.is_empty() - } else { - false - } - } else { - true - } -} diff --git a/macros/src/tests.rs b/macros/src/tests.rs new file mode 100644 index 00000000..e9e3326e --- /dev/null +++ b/macros/src/tests.rs @@ -0,0 +1,4 @@ +// NOTE these tests are specific to the Cortex-M port; `rtic-syntax` has a more extensive test suite +// that tests functionality common to all the RTIC ports + +mod single; diff --git a/macros/src/tests/single.rs b/macros/src/tests/single.rs new file mode 100644 index 00000000..97cbbb3f --- /dev/null +++ b/macros/src/tests/single.rs @@ -0,0 +1,34 @@ +use quote::quote; +use rtic_syntax::Settings; + +#[test] +fn analyze() { + let mut settings = Settings::default(); + settings.parse_extern_interrupt = true; + let (app, analysis) = rtic_syntax::parse2( + quote!(device = pac), + quote!( + mod app { + #[task(priority = 1)] + fn a(_: a::Context) {} + + #[task(priority = 2)] + fn b(_: b::Context) {} + + // First interrupt is assigned to the highest priority dispatcher + extern "C" { + fn B(); + fn A(); + } + } + ), + settings, + ) + .unwrap(); + + let analysis = crate::analyze::app(analysis, &app); + let interrupts = &analysis.interrupts; + assert_eq!(interrupts.len(), 2); + assert_eq!(interrupts[&2].to_string(), "B"); + assert_eq!(interrupts[&1].to_string(), "A"); +} |