diff options
Diffstat (limited to 'macros/src')
-rw-r--r-- | macros/src/analyze.rs | 262 | ||||
-rw-r--r-- | macros/src/check.rs | 182 | ||||
-rw-r--r-- | macros/src/codegen.rs | 1815 | ||||
-rw-r--r-- | macros/src/lib.rs | 367 | ||||
-rw-r--r-- | macros/src/syntax.rs | 1235 | ||||
-rw-r--r-- | macros/src/trans.rs | 631 |
6 files changed, 3612 insertions, 880 deletions
diff --git a/macros/src/analyze.rs b/macros/src/analyze.rs index 666dc306..04b462fa 100644 --- a/macros/src/analyze.rs +++ b/macros/src/analyze.rs @@ -1,77 +1,243 @@ -use std::cmp; -use std::collections::HashMap; +use std::{ + cmp, + collections::{HashMap, HashSet}, +}; -use syn::Ident; +use syn::{Attribute, Ident, Type}; -use check::App; +use syntax::{App, Idents}; pub type Ownerships = HashMap<Ident, Ownership>; +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 { - /// Owned or co-owned by tasks that run at the same priority + // NOTE priorities and ceilings are "logical" (0 = lowest priority, 255 = highest priority) Owned { priority: u8 }, - /// Shared by tasks that run at different priorities. - /// - /// `ceiling` is the maximum value across all the task priorities Shared { ceiling: u8 }, } impl Ownership { - pub fn ceiling(&self) -> u8 { + pub fn needs_lock(&self, priority: u8) -> bool { match *self { - Ownership::Owned { priority } => priority, - Ownership::Shared { ceiling } => ceiling, - } - } + Ownership::Owned { .. } => false, + Ownership::Shared { ceiling } => { + debug_assert!(ceiling >= priority); - pub fn is_owned(&self) -> bool { - match *self { - Ownership::Owned { .. } => true, - _ => false, + priority < ceiling + } } } } -pub fn app(app: &App) -> Ownerships { - let mut ownerships = HashMap::new(); +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, +} - for resource in &app.idle.resources { - ownerships.insert(resource.clone(), Ownership::Owned { priority: 0 }); - } +/// Priority -> Dispatcher +pub type Dispatchers = HashMap<u8, Dispatcher>; - for task in app.tasks.values() { - for resource in task.resources.iter() { - if let Some(ownership) = ownerships.get_mut(resource) { - match *ownership { - Ownership::Owned { priority } => { - if priority == task.priority { - *ownership = Ownership::Owned { priority }; - } else { - *ownership = Ownership::Shared { - ceiling: cmp::max(priority, task.priority), - }; - } - } - Ownership::Shared { ceiling } => { - if task.priority > ceiling { - *ownership = Ownership::Shared { - ceiling: task.priority, - }; +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::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()); } } } + } + + 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; + } + } - continue; + // 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()); + } + } - ownerships.insert( - resource.clone(), - Ownership::Owned { - priority: task.priority, - }, - ); + // 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()); } } - ownerships + // 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 + } + } + + 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, + }, + } +} + +pub struct TimerQueue { + pub capacity: u8, + pub ceiling: u8, + pub priority: u8, + pub tasks: Idents, } diff --git a/macros/src/check.rs b/macros/src/check.rs index b81fc4d4..f2832207 100644 --- a/macros/src/check.rs +++ b/macros/src/check.rs @@ -1,95 +1,115 @@ -use std::collections::HashMap; +use std::{collections::HashSet, iter}; -use syn::{Ident, Path}; -use syntax::check::{self, Idents, Idle, Init, Statics}; -use syntax::{self, Result}; +use proc_macro2::Span; +use syn::parse; -pub struct App { - pub device: Path, - pub idle: Idle, - pub init: Init, - pub resources: Statics, - pub tasks: Tasks, -} - -pub type Tasks = HashMap<Ident, Task>; +use syntax::App; -#[allow(non_camel_case_types)] -pub enum Exception { - PENDSV, - SVCALL, - SYS_TICK, -} - -impl Exception { - pub fn from(s: &str) -> Option<Self> { - Some(match s { - "PENDSV" => Exception::PENDSV, - "SVCALL" => Exception::SVCALL, - "SYS_TICK" => Exception::SYS_TICK, - _ => return None, - }) +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<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", + )); + } } - pub fn nr(&self) -> usize { - match *self { - Exception::PENDSV => 14, - Exception::SVCALL => 11, - Exception::SYS_TICK => 15, + // 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 enum Kind { - Exception(Exception), - Interrupt { enabled: bool }, -} - -pub struct Task { - pub kind: Kind, - pub path: Path, - pub priority: u8, - pub resources: Idents, -} - -pub fn app(app: check::App) -> Result<App> { - let app = App { - device: app.device, - idle: app.idle, - init: app.init, - resources: app.resources, - tasks: app.tasks - .into_iter() - .map(|(k, v)| { - let v = ::check::task(&k.to_string(), v)?; - Ok((k, v)) - }) - .collect::<Result<_>>()?, - }; + // Check that all late resources have been initialized in `#[init]` + 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) { + return Err(parse::Error::new( + res.span(), + "late resources MUST be initialized at the end of `init`", + )); + } + } - Ok(app) -} + // Check that all referenced tasks have been declared + for task in app + .idle + .as_ref() + .map(|idle| -> Box<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", + )); + } + } -fn task(name: &str, task: syntax::check::Task) -> Result<Task> { - let kind = match Exception::from(name) { - Some(e) => { - ensure!( - task.enabled.is_none(), - "`enabled` field is not valid for exceptions" - ); + // 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" }, + ), + )); + } - Kind::Exception(e) + // Check that free interrupts are not being used + for int in app.interrupts.keys() { + if app.free_interrupts.contains_key(int) { + return Err(parse::Error::new( + int.span(), + "free interrupts (`extern { .. }`) can't be used as interrupt handlers", + )); } - None => Kind::Interrupt { - enabled: task.enabled.unwrap_or(true), - }, - }; + } - Ok(Task { - kind, - path: task.path, - priority: task.priority.unwrap_or(1), - resources: task.resources, - }) + Ok(()) } diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs new file mode 100644 index 00000000..ff1062ae --- /dev/null +++ b/macros/src/codegen.rs @@ -0,0 +1,1815 @@ +use proc_macro::TokenStream; +use std::{ + collections::HashMap, + sync::atomic::{AtomicUsize, Ordering}, + time::{SystemTime, UNIX_EPOCH}, +}; + +use proc_macro2::Span; +use quote::quote; +use rand::{Rng, SeedableRng}; +use syn::{ArgCaptured, Ident, IntSuffix, LitInt}; + +use analyze::{Analysis, Ownership}; +use syntax::{App, Idents, Static}; + +// NOTE to avoid polluting the user namespaces we map some identifiers to pseudo-hygienic names. +// In some instances we also use the pseudo-hygienic names for safety, for example the user should +// not modify the priority field of resources. +type Aliases = HashMap<Ident, Ident>; + +struct Context { + // Alias + #[cfg(feature = "timer-queue")] + baseline: Ident, + // Dispatcher -> Alias (`enum`) + enums: HashMap<u8, Ident>, + // Task -> Alias (`static` / resource) + free_queues: Aliases, + // Alias (`fn`) + idle: Ident, + // Alias (`fn`) + init: Ident, + // Task -> Alias (`static`) + inputs: Aliases, + // Alias + priority: Ident, + // Dispatcher -> Alias (`static` / resource) + ready_queues: HashMap<u8, Ident>, + // For non-singletons this maps the resource name to its `static mut` variable name + statics: Aliases, + /// Task -> Alias (`struct`) + resources: HashMap<Kind, Resources>, + // Task -> Alias (`static`) + #[cfg(feature = "timer-queue")] + scheduleds: Aliases, + // Task -> Alias (`fn`) + spawn_fn: Aliases, + // Alias (`enum`) + schedule_enum: Ident, + // Task -> Alias (`fn`) + schedule_fn: Aliases, + tasks: Aliases, + // Alias (`struct` / `static mut`) + timer_queue: Ident, +} + +impl Default for Context { + fn default() -> Self { + Context { + #[cfg(feature = "timer-queue")] + baseline: mk_ident(), + enums: HashMap::new(), + free_queues: Aliases::new(), + idle: mk_ident(), + init: mk_ident(), + inputs: Aliases::new(), + priority: mk_ident(), + ready_queues: HashMap::new(), + statics: Aliases::new(), + resources: HashMap::new(), + #[cfg(feature = "timer-queue")] + scheduleds: Aliases::new(), + spawn_fn: Aliases::new(), + schedule_enum: mk_ident(), + schedule_fn: Aliases::new(), + tasks: Aliases::new(), + timer_queue: mk_ident(), + } + } +} + +struct Resources { + alias: Ident, + decl: proc_macro2::TokenStream, +} + +pub fn app(app: &App, analysis: &Analysis) -> TokenStream { + let mut ctxt = Context::default(); + + let device = &app.args.device; + + let resources = resources(&mut ctxt, &app, analysis); + + let tasks = tasks(&mut ctxt, &app, analysis); + + let (dispatchers_data, dispatchers) = dispatchers(&mut ctxt, &app, analysis); + + let init_fn = init(&mut ctxt, &app, analysis); + let init_arg = if cfg!(feature = "timer-queue") { + quote!(rtfm::Peripherals { + CBP: p.CBP, + CPUID: p.CPUID, + DCB: &mut p.DCB, + FPB: p.FPB, + FPU: p.FPU, + ITM: p.ITM, + MPU: p.MPU, + SCB: &mut p.SCB, + TPIU: p.TPIU, + }) + } else { + quote!(rtfm::Peripherals { + CBP: p.CBP, + CPUID: p.CPUID, + DCB: p.DCB, + DWT: p.DWT, + FPB: p.FPB, + FPU: p.FPU, + ITM: p.ITM, + MPU: p.MPU, + SCB: &mut p.SCB, + SYST: p.SYST, + TPIU: p.TPIU, + }) + }; + + let post_init = post_init(&ctxt, &app, analysis); + + let (idle_fn, idle_expr) = idle(&mut ctxt, &app, analysis); + + let exceptions = exceptions(&mut ctxt, app, analysis); + + let (root_interrupts, scoped_interrupts) = interrupts(&mut ctxt, app, analysis); + + let spawn = spawn(&mut ctxt, app, analysis); + + let schedule = match () { + #[cfg(feature = "timer-queue")] + () => schedule(&ctxt, app), + #[cfg(not(feature = "timer-queue"))] + () => quote!(), + }; + + let timer_queue = timer_queue(&ctxt, app, analysis); + + let pre_init = pre_init(&ctxt, analysis); + + let assertions = assertions(app, analysis); + + let init = &ctxt.init; + quote!( + #resources + + #spawn + + #timer_queue + + #schedule + + #dispatchers_data + + #(#exceptions)* + + #root_interrupts + + // We put these items into a pseudo-module to avoid a collision between the `interrupt` + // import and user code + const APP: () = { + use #device::interrupt; + + #scoped_interrupts + + #(#dispatchers)* + }; + + #(#tasks)* + + #init_fn + + #idle_fn + + #[allow(unsafe_code)] + #[rtfm::export::entry] + #[doc(hidden)] + unsafe fn main() -> ! { + #assertions + + rtfm::export::interrupt::disable(); + + #pre_init + + #init(#init_arg); + + #post_init + + rtfm::export::interrupt::enable(); + + #idle_expr + } + ) + .into() +} + +fn resources(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream { + let mut items = vec![]; + let mut module = vec![]; + for (name, res) in &app.resources { + let attrs = &res.attrs; + let mut_ = &res.mutability; + let ty = &res.ty; + let expr = &res.expr; + + if res.singleton { + items.push(quote!( + #(#attrs)* + pub static #mut_ #name: #ty = #expr; + )); + + let alias = mk_ident(); + if let Some(Ownership::Shared { ceiling }) = analysis.ownerships.get(name) { + items.push(mk_resource( + ctxt, + name, + quote!(#name), + *ceiling, + quote!(&mut <#name as owned_singleton::Singleton>::new()), + app, + Some(&mut module), + )) + } + + ctxt.statics.insert(name.clone(), alias); + } else { + let alias = mk_ident(); + let symbol = format!("{}::{}", name, alias); + + items.push( + expr.as_ref() + .map(|expr| { + quote!( + #(#attrs)* + #[export_name = #symbol] + static mut #alias: #ty = #expr; + ) + }) + .unwrap_or_else(|| { + quote!( + #(#attrs)* + #[export_name = #symbol] + static mut #alias: rtfm::export::MaybeUninit<#ty> = + rtfm::export::MaybeUninit::uninitialized(); + ) + }), + ); + + if let Some(Ownership::Shared { ceiling }) = analysis.ownerships.get(name) { + if res.mutability.is_some() { + let ptr = if res.expr.is_none() { + quote!(unsafe { #alias.get_mut() }) + } else { + quote!(unsafe { &mut #alias }) + }; + + items.push(mk_resource( + ctxt, + name, + quote!(#ty), + *ceiling, + ptr, + app, + Some(&mut module), + )); + } + } + + ctxt.statics.insert(name.clone(), alias); + } + } + + if !module.is_empty() { + items.push(quote!( + /// Resource proxies + pub mod resources { + #(#module)* + })); + } + + quote!(#(#items)*) +} + +fn init(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream { + let attrs = &app.init.attrs; + let locals = mk_locals(&app.init.statics, true); + let stmts = &app.init.stmts; + let assigns = app + .init + .assigns + .iter() + .map(|assign| { + if app + .resources + .get(&assign.left) + .map(|r| r.expr.is_none()) + .unwrap_or(false) + { + let alias = &ctxt.statics[&assign.left]; + let expr = &assign.right; + quote!(unsafe { #alias.set(#expr); }) + } else { + let left = &assign.left; + let right = &assign.right; + quote!(#left = #right;) + } + }) + .collect::<Vec<_>>(); + + let prelude = prelude( + ctxt, + Kind::Init, + &app.init.args.resources, + &app.init.args.spawn, + &app.init.args.schedule, + app, + 255, + analysis, + ); + + let module = module( + ctxt, + Kind::Init, + !app.init.args.schedule.is_empty(), + !app.init.args.spawn.is_empty(), + app, + ); + + #[cfg(feature = "timer-queue")] + let baseline = &ctxt.baseline; + let baseline_let = match () { + #[cfg(feature = "timer-queue")] + () => quote!(let #baseline = rtfm::Instant::artificial(0);), + + #[cfg(not(feature = "timer-queue"))] + () => quote!(), + }; + + let start_let = match () { + #[cfg(feature = "timer-queue")] + () => quote!( + #[allow(unused_variables)] + let start = #baseline; + ), + #[cfg(not(feature = "timer-queue"))] + () => quote!(), + }; + + let unsafety = &app.init.unsafety; + let device = &app.args.device; + let init = &ctxt.init; + let name = format!("init::{}", init); + quote!( + #module + + #(#attrs)* + #[export_name = #name] + #unsafety fn #init(mut core: rtfm::Peripherals) { + #(#locals)* + + #baseline_let + + #prelude + + let mut device = unsafe { #device::Peripherals::steal() }; + + #start_let + + #(#stmts)* + + #(#assigns)* + } + ) +} + +fn post_init(ctxt: &Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream { + let mut exprs = vec![]; + + // TODO turn the assertions that check that the priority is not larger than what's supported by + // the device into compile errors + let device = &app.args.device; + let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS); + for (name, interrupt) in &app.interrupts { + let priority = interrupt.args.priority; + exprs.push(quote!(p.NVIC.enable(#device::Interrupt::#name))); + exprs.push(quote!(assert!(#priority <= (1 << #nvic_prio_bits)))); + exprs.push(quote!(p.NVIC.set_priority( + #device::Interrupt::#name, + ((1 << #nvic_prio_bits) - #priority) << (8 - #nvic_prio_bits), + ))); + } + + for (name, exception) in &app.exceptions { + let priority = exception.args.priority; + exprs.push(quote!(assert!(#priority <= (1 << #nvic_prio_bits)))); + exprs.push(quote!(p.SCB.set_priority( + rtfm::export::SystemHandler::#name, + ((1 << #nvic_prio_bits) - #priority) << (8 - #nvic_prio_bits), + ))); + } + + if !analysis.timer_queue.tasks.is_empty() { + let priority = analysis.timer_queue.priority; + exprs.push(quote!(assert!(#priority <= (1 << #nvic_prio_bits)))); + exprs.push(quote!(p.SCB.set_priority( + rtfm::export::SystemHandler::SysTick, + ((1 << #nvic_prio_bits) - #priority) << (8 - #nvic_prio_bits), + ))); + } + + for (priority, dispatcher) in &analysis.dispatchers { + let name = &dispatcher.interrupt; + exprs.push(quote!(p.NVIC.enable(#device::Interrupt::#name))); + exprs.push(quote!(assert!(#priority <= (1 << #nvic_prio_bits)))); + exprs.push(quote!(p.NVIC.set_priority( + #device::Interrupt::#name, + ((1 << #nvic_prio_bits) - #priority) << (8 - #nvic_prio_bits), + ))); + } + + if app.idle.is_none() { + // Set SLEEPONEXIT bit to enter sleep mode when returning from ISR + exprs.push(quote!(p.SCB.scr.modify(|r| r | 1 << 1))); + } + + // Enable and start the system timer + if !analysis.timer_queue.tasks.is_empty() { + let tq = &ctxt.timer_queue; + exprs.push(quote!(#tq.get_mut().syst.set_clock_source(rtfm::export::SystClkSource::Core))); + exprs.push(quote!(#tq.get_mut().syst.enable_counter())); + } + + // Enable cycle counter + if cfg!(feature = "timer-queue") { + exprs.push(quote!(p.DCB.enable_trace())); + exprs.push(quote!(p.DWT.enable_cycle_counter())); + } + + quote!(#(#exprs;)*) +} + +/// This function creates creates a module for `init` / `idle` / a `task` (see `kind` argument) +fn module( + ctxt: &mut Context, + kind: Kind, + schedule: bool, + spawn: bool, + app: &App, +) -> proc_macro2::TokenStream { + let mut items = vec![]; + let mut fields = vec![]; + + let name = kind.ident(); + let priority = &ctxt.priority; + let device = &app.args.device; + + let mut lt = None; + match kind { + Kind::Init => { + if cfg!(feature = "timer-queue") { + fields.push(quote!( + /// System start time = `Instant(0 /* cycles */)` + pub start: rtfm::Instant, + )); + } + + fields.push(quote!( + /// Core (Cortex-M) peripherals + pub core: rtfm::Peripherals<'a>, + /// Device specific peripherals + pub device: #device::Peripherals, + )); + lt = Some(quote!('a)); + } + Kind::Idle => {} + Kind::Exception(_) | Kind::Interrupt(_) => { + if cfg!(feature = "timer-queue") { + fields.push(quote!( + /// Time at which this handler started executing + pub start: rtfm::Instant, + )); + } + } + Kind::Task(_) => { + if cfg!(feature = "timer-queue") { + fields.push(quote!( + /// The time at which this task was scheduled to run + pub scheduled: rtfm::Instant, + )); + } + } + } + + if schedule { + lt = Some(quote!('a)); + + fields.push(quote!( + /// Tasks that can be scheduled from this context + pub schedule: Schedule<'a>, + )); + + items.push(quote!( + /// Tasks that can be scheduled from this context + #[derive(Clone, Copy)] + pub struct Schedule<'a> { + #[doc(hidden)] + pub #priority: &'a core::cell::Cell<u8>, + } + )); + } + + if spawn { + lt = Some(quote!('a)); + + fields.push(quote!( + /// Tasks that can be spawned from this context + pub spawn: Spawn<'a>, + )); + + if kind.is_idle() { + items.push(quote!( + /// Tasks that can be spawned from this context + #[derive(Clone, Copy)] + pub struct Spawn<'a> { + #[doc(hidden)] + pub #priority: &'a core::cell::Cell<u8>, + } + )); + } else { + let baseline_field = match () { + #[cfg(feature = "timer-queue")] + () => { + let baseline = &ctxt.baseline; + quote!( + #[doc(hidden)] + pub #baseline: rtfm::Instant, + ) + } + #[cfg(not(feature = "timer-queue"))] + () => quote!(), + }; + + items.push(quote!( + /// Tasks that can be spawned from this context + #[derive(Clone, Copy)] + pub struct Spawn<'a> { + #baseline_field + #[doc(hidden)] + pub #priority: &'a core::cell::Cell<u8>, + } + )); + } + } + + let mut root = None; + if let Some(resources) = ctxt.resources.get(&kind) { + lt = Some(quote!('a)); + + root = Some(resources.decl.clone()); + + let alias = &resources.alias; + items.push(quote!( + #[doc(inline)] + pub use super::#alias as Resources; + )); + + fields.push(quote!( + /// Resources available in this context + pub resources: Resources<'a>, + )); + }; + + let doc = match kind { + Kind::Exception(_) => "Exception handler", + Kind::Idle => "Idle loop", + Kind::Init => "Initialization function", + Kind::Interrupt(_) => "Interrupt handler", + Kind::Task(_) => "Software task", + }; + + quote!( + #root + + #[doc = #doc] + pub mod #name { + /// Variables injected into this context by the `app` attribute + pub struct Context<#lt> { + #(#fields)* + } + + #(#items)* + } + ) +} + +/// The prelude injects `resources`, `spawn`, `schedule` and `start` / `scheduled` (all values) into +/// a function scope +fn prelude( + ctxt: &mut Context, + kind: Kind, + resources: &Idents, + spawn: &Idents, + schedule: &Idents, + app: &App, + logical_prio: u8, + analysis: &Analysis, +) -> proc_macro2::TokenStream { + let mut items = vec![]; + + let lt = if kind.runs_once() { + quote!('static) + } else { + quote!('a) + }; + + let module = kind.ident(); + + let priority = &ctxt.priority; + if !resources.is_empty() { + let mut defs = vec![]; + let mut exprs = vec![]; + + // NOTE This field is just to avoid unused type parameter errors around `'a` + defs.push(quote!(#[allow(dead_code)] #priority: &'a core::cell::Cell<u8>)); + exprs.push(quote!(#priority)); + + let mut may_call_lock = false; + let mut needs_unsafe = false; + for name in resources { + let res = &app.resources[name]; + let initialized = res.expr.is_some(); + let singleton = res.singleton; + let mut_ = res.mutability; + let ty = &res.ty; + + if kind.is_init() { + let mut force_mut = false; + if !analysis.ownerships.contains_key(name) { + // owned by Init + if singleton { + needs_unsafe = true; + defs.push(quote!(pub #name: #name)); + exprs.push(quote!(#name: <#name as owned_singleton::Singleton>::new())); + continue; + } else { + defs.push(quote!(pub #name: &'static #mut_ #ty)); + } + } else { + // owned by someone else + if singleton { + needs_unsafe = true; + defs.push(quote!(pub #name: &'a mut #name)); + exprs + .push(quote!(#name: &mut <#name as owned_singleton::Singleton>::new())); + continue; + } else { + force_mut = true; + defs.push(quote!(pub #name: &'a mut #ty)); + } + } + + let alias = &ctxt.statics[name]; + // Resources assigned to init are always const initialized + needs_unsafe = true; + if force_mut { + exprs.push(quote!(#name: &mut #alias)); + } else { + exprs.push(quote!(#name: &#mut_ #alias)); + } + } else { + let ownership = &analysis.ownerships[name]; + + if ownership.needs_lock(logical_prio) { + may_call_lock = true; + if singleton { + if mut_.is_none() { + needs_unsafe = true; + defs.push(quote!(pub #name: &'a #name)); + exprs + .push(quote!(#name: &<#name as owned_singleton::Singleton>::new())); + continue; + } else { + // Generate a resource proxy + defs.push(quote!(pub #name: resources::#name<'a>)); + exprs.push(quote!(#name: resources::#name { #priority })); + continue; + } + } else { + if mut_.is_none() { + defs.push(quote!(pub #name: &'a #ty)); + } else { + // Generate a resource proxy + defs.push(quote!(pub #name: resources::#name<'a>)); + exprs.push(quote!(#name: resources::#name { #priority })); + continue; + } + } + } else { + if singleton { + if kind.runs_once() { + needs_unsafe = true; + defs.push(quote!(pub #name: #name)); + exprs.push(quote!(#name: <#name as owned_singleton::Singleton>::new())); + } else { + needs_unsafe = true; + defs.push(quote!(pub #name: &'a mut #name)); + exprs.push( + quote!(#name: &mut <#name as owned_singleton::Singleton>::new()), + ); + } + continue; + } else { + defs.push(quote!(pub #name: &#lt #mut_ #ty)); + } + } + + let alias = &ctxt.statics[name]; + needs_unsafe = true; + if initialized { + exprs.push(quote!(#name: &#mut_ #alias)); + } else { + let method = if mut_.is_some() { + quote!(get_mut) + } else { + quote!(get_ref) + }; + exprs.push(quote!(#name: #alias.#method() )); + } + } + } + + let alias = mk_ident(); + let unsafety = if needs_unsafe { + Some(quote!(unsafe)) + } else { + None + }; + + let doc = format!("`{}::Resources`", kind.ident().to_string()); + let decl = quote!( + #[doc = #doc] + #[allow(non_snake_case)] + pub struct #alias<'a> { #(#defs,)* } + ); + items.push(quote!( + #[allow(unused_variables)] + #[allow(unsafe_code)] + #[allow(unused_mut)] + let mut resources = #unsafety { #alias { #(#exprs,)* } }; + )); + + ctxt.resources + .insert(kind.clone(), Resources { alias, decl }); + + if may_call_lock { + items.push(quote!( + use rtfm::Mutex; + )); + } + } + + if !spawn.is_empty() { + // Populate `spawn_fn` + for task in spawn { + if ctxt.spawn_fn.contains_key(task) { + continue; + } + + ctxt.spawn_fn.insert(task.clone(), mk_ident()); + } + + if kind.is_idle() { + items.push(quote!( + #[allow(unused_variables)] + let spawn = #module::Spawn { #priority }; + )); + } else { + let baseline_expr = match () { + #[cfg(feature = "timer-queue")] + () => { + let baseline = &ctxt.baseline; + quote!(#baseline) + } + #[cfg(not(feature = "timer-queue"))] + () => quote!(), + }; + items.push(quote!( + #[allow(unused_variables)] + let spawn = #module::Spawn { #priority, #baseline_expr }; + )); + } + } + + if !schedule.is_empty() { + // Populate `schedule_fn` + for task in schedule { + if ctxt.schedule_fn.contains_key(task) { + continue; + } + + ctxt.schedule_fn.insert(task.clone(), mk_ident()); + } + + items.push(quote!( + #[allow(unused_imports)] + use rtfm::U32Ext; + + #[allow(unused_variables)] + let schedule = #module::Schedule { #priority }; + )); + } + + if items.is_empty() { + quote!() + } else { + quote!( + let ref #priority = core::cell::Cell::new(#logical_prio); + + #(#items)* + ) + } +} + +fn idle( + ctxt: &mut Context, + app: &App, + analysis: &Analysis, +) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { + if let Some(idle) = app.idle.as_ref() { + let attrs = &idle.attrs; + let locals = mk_locals(&idle.statics, true); + let stmts = &idle.stmts; + + let prelude = prelude( + ctxt, + Kind::Idle, + &idle.args.resources, + &idle.args.spawn, + &idle.args.schedule, + app, + 0, + analysis, + ); + + let module = module( + ctxt, + Kind::Idle, + !idle.args.schedule.is_empty(), + !idle.args.spawn.is_empty(), + app, + ); + + let unsafety = &idle.unsafety; + let idle = &ctxt.idle; + + let name = format!("idle::{}", idle); + ( + quote!( + #module + + #(#attrs)* + #[export_name = #name] + #unsafety fn #idle() -> ! { + #(#locals)* + + #prelude + + #(#stmts)* + }), + quote!(#idle()), + ) + } else { + ( + quote!(), + quote!(loop { + rtfm::export::wfi(); + }), + ) + } +} + +fn exceptions(ctxt: &mut Context, app: &App, analysis: &Analysis) -> Vec<proc_macro2::TokenStream> { + app.exceptions + .iter() + .map(|(ident, exception)| { + let attrs = &exception.attrs; + let statics = &exception.statics; + let stmts = &exception.stmts; + + let prelude = prelude( + ctxt, + Kind::Exception(ident.clone()), + &exception.args.resources, + &exception.args.spawn, + &exception.args.schedule, + app, + exception.args.priority, + analysis, + ); + + let module = module( + ctxt, + Kind::Exception(ident.clone()), + !exception.args.schedule.is_empty(), + !exception.args.spawn.is_empty(), + app, + ); + + #[cfg(feature = "timer-queue")] + let baseline = &ctxt.baseline; + let baseline_let = match () { + #[cfg(feature = "timer-queue")] + () => quote!(let #baseline = rtfm::Instant::now();), + #[cfg(not(feature = "timer-queue"))] + () => quote!(), + }; + + let start_let = match () { + #[cfg(feature = "timer-queue")] + () => quote!( + #[allow(unused_variables)] + let start = #baseline; + ), + #[cfg(not(feature = "timer-queue"))] + () => quote!(), + }; + + let unsafety = &exception.unsafety; + quote!( + #module + + #[rtfm::export::exception] + #[doc(hidden)] + #(#attrs)* + #unsafety fn #ident() { + #(#statics)* + + #baseline_let + + #prelude + + #start_let + + rtfm::export::run(move || { + #(#stmts)* + }) + }) + }) + .collect() +} + +fn interrupts( + ctxt: &mut Context, + app: &App, + analysis: &Analysis, +) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { + let mut root = vec![]; + let mut scoped = vec![]; + + for (ident, interrupt) in &app.interrupts { + let attrs = &interrupt.attrs; + let statics = &interrupt.statics; + let stmts = &interrupt.stmts; + + let prelude = prelude( + ctxt, + Kind::Interrupt(ident.clone()), + &interrupt.args.resources, + &interrupt.args.spawn, + &interrupt.args.schedule, + app, + interrupt.args.priority, + analysis, + ); + + root.push(module( + ctxt, + Kind::Interrupt(ident.clone()), + !interrupt.args.schedule.is_empty(), + !interrupt.args.spawn.is_empty(), + app, + )); + + #[cfg(feature = "timer-queue")] + let baseline = &ctxt.baseline; + let baseline_let = match () { + #[cfg(feature = "timer-queue")] + () => quote!(let #baseline = rtfm::Instant::now();), + #[cfg(not(feature = "timer-queue"))] + () => quote!(), + }; + + let start_let = match () { + #[cfg(feature = "timer-queue")] + () => quote!( + #[allow(unused_variables)] + let start = #baseline; + ), + #[cfg(not(feature = "timer-queue"))] + () => quote!(), + }; + + let unsafety = &interrupt.unsafety; + scoped.push(quote!( + #[interrupt] + #(#attrs)* + #unsafety fn #ident() { + #(#statics)* + + #baseline_let + + #prelude + + #start_let + + rtfm::export::run(move || { + #(#stmts)* + }) + })); + } + + (quote!(#(#root)*), quote!(#(#scoped)*)) +} + +fn tasks(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream { + let mut items = vec![]; + for (name, task) in &app.tasks { + #[cfg(feature = "timer-queue")] + let scheduleds_alias = mk_ident(); + let free_alias = mk_ident(); + let inputs_alias = mk_ident(); + let task_alias = mk_ident(); + + let attrs = &task.attrs; + let inputs = &task.inputs; + let locals = mk_locals(&task.statics, false); + let stmts = &task.stmts; + + let prelude = prelude( + ctxt, + Kind::Task(name.clone()), + &task.args.resources, + &task.args.spawn, + &task.args.schedule, + app, + task.args.priority, + analysis, + ); + + let ty = tuple_ty(inputs); + + let capacity_lit = mk_capacity_literal(analysis.capacities[name]); + let capacity_ty = mk_typenum_capacity(analysis.capacities[name], true); + + let resource = mk_resource( + ctxt, + &free_alias, + quote!(rtfm::export::FreeQueue<#capacity_ty>), + *analysis.free_queues.get(name).unwrap_or(&0), + quote!(#free_alias.get_mut()), + app, + None, + ); + + let scheduleds_static = match () { + #[cfg(feature = "timer-queue")] + () => { + let scheduleds_symbol = format!("{}::SCHEDULED_TIMES::{}", name, scheduleds_alias); + + quote!( + #[export_name = #scheduleds_symbol] + static mut #scheduleds_alias: + rtfm::export::MaybeUninit<[rtfm::Instant; #capacity_lit]> = + rtfm::export::MaybeUninit::uninitialized(); + ) + } + #[cfg(not(feature = "timer-queue"))] + () => quote!(), + }; + + let scheduled_let = match () { + #[cfg(feature = "timer-queue")] + () => { + let baseline = &ctxt.baseline; + quote!(let scheduled = #baseline;) + } + #[cfg(not(feature = "timer-queue"))] + () => quote!(), + }; + + let baseline_arg = match () { + #[cfg(feature = "timer-queue")] + () => { + let baseline = &ctxt.baseline; + quote!(#baseline: rtfm::Instant,) + } + #[cfg(not(feature = "timer-queue"))] + () => quote!(), + }; + let task_symbol = format!("{}::{}", name, task_alias); + let inputs_symbol = format!("{}::INPUTS::{}", name, inputs_alias); + let free_symbol = format!("{}::FREE_QUEUE::{}", name, free_alias); + let unsafety = &task.unsafety; + items.push(quote!( + // FIXME(MaybeUninit) MaybeUninit won't be necessary when core::mem::MaybeUninit + // stabilizes because heapless constructors will work in const context + #[export_name = #free_symbol] + static mut #free_alias: rtfm::export::MaybeUninit< + rtfm::export::FreeQueue<#capacity_ty> + > = rtfm::export::MaybeUninit::uninitialized(); + + #resource + + #[export_name = #inputs_symbol] + static mut #inputs_alias: rtfm::export::MaybeUninit<[#ty; #capacity_lit]> = + rtfm::export::MaybeUninit::uninitialized(); + + #scheduleds_static + + #(#attrs)* + #[export_name = #task_symbol] + #unsafety fn #task_alias(#baseline_arg #(#inputs,)*) { + #(#locals)* + + #prelude + + #scheduled_let + + #(#stmts)* + } + )); + + items.push(module( + ctxt, + Kind::Task(name.clone()), + !task.args.schedule.is_empty(), + !task.args.spawn.is_empty(), + app, + )); + + #[cfg(feature = "timer-queue")] + ctxt.scheduleds.insert(name.clone(), scheduleds_alias); + ctxt.free_queues.insert(name.clone(), free_alias); + ctxt.inputs.insert(name.clone(), inputs_alias); + ctxt.tasks.insert(name.clone(), task_alias); + } + + quote!(#(#items)*) +} + +fn dispatchers( + ctxt: &mut Context, + app: &App, + analysis: &Analysis, +) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { + let mut data = vec![]; + let mut dispatchers = vec![]; + + for (level, dispatcher) in &analysis.dispatchers { + let ready_alias = mk_ident(); + let enum_alias = mk_ident(); + let tasks = &dispatcher.tasks; + let capacity = mk_typenum_capacity(dispatcher.capacity, true); + + let symbol = format!("P{}::READY_QUEUE::{}", level, ready_alias); + let e = quote!(rtfm::export); + let ty = quote!(#e::ReadyQueue<#enum_alias, #capacity>); + let ceiling = *analysis.ready_queues.get(&level).unwrap_or(&0); + let resource = mk_resource( + ctxt, + &ready_alias, + ty.clone(), + ceiling, + quote!(#ready_alias.get_mut()), + app, + None, + ); + data.push(quote!( + #[allow(dead_code)] + #[allow(non_camel_case_types)] + enum #enum_alias { #(#tasks,)* } + + #[export_name = #symbol] + static mut #ready_alias: #e::MaybeUninit<#ty> = #e::MaybeUninit::uninitialized(); + + #resource + )); + + let interrupt = &dispatcher.interrupt; + + let arms = dispatcher + .tasks + .iter() + .map(|task| { + let inputs = &ctxt.inputs[task]; + let free = &ctxt.free_queues[task]; + let pats = tuple_pat(&app.tasks[task].inputs); + let alias = &ctxt.tasks[task]; + + let baseline_let; + let call; + match () { + #[cfg(feature = "timer-queue")] + () => { + let scheduleds = &ctxt.scheduleds[task]; + baseline_let = quote!( + let baseline = + ptr::read(#scheduleds.get_ref().get_unchecked(usize::from(index))); + ); + call = quote!(#alias(baseline, #pats)); + } + #[cfg(not(feature = "timer-queue"))] + () => { + baseline_let = quote!(); + call = quote!(#alias(#pats)); + } + }; + + quote!(#enum_alias::#task => { + #baseline_let + let input = ptr::read(#inputs.get_ref().get_unchecked(usize::from(index))); + #free.get_mut().split().0.enqueue_unchecked(index); + let (#pats) = input; + #call + }) + }) + .collect::<Vec<_>>(); + + let attrs = &dispatcher.attrs; + dispatchers.push(quote!( + #(#attrs)* + #[interrupt] + unsafe fn #interrupt() { + use core::ptr; + + rtfm::export::run(|| { + while let Some((task, index)) = #ready_alias.get_mut().split().1.dequeue() { + match task { + #(#arms)* + } + } + }); + } + )); + + ctxt.ready_queues.insert(*level, ready_alias); + ctxt.enums.insert(*level, enum_alias); + } + + (quote!(#(#data)*), quote!(#(#dispatchers)*)) +} + +fn spawn(ctxt: &Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream { + let mut items = vec![]; + + // Generate `spawn` functions + let device = &app.args.device; + let priority = &ctxt.priority; + #[cfg(feature = "timer-queue")] + let baseline = &ctxt.baseline; + for (task, alias) in &ctxt.spawn_fn { + let free = &ctxt.free_queues[task]; + let level = app.tasks[task].args.priority; + let ready = &ctxt.ready_queues[&level]; + let enum_ = &ctxt.enums[&level]; + let dispatcher = &analysis.dispatchers[&level].interrupt; + let inputs = &ctxt.inputs[task]; + let args = &app.tasks[task].inputs; + let ty = tuple_ty(args); + let pats = tuple_pat(args); + + let scheduleds_write = match () { + #[cfg(feature = "timer-queue")] + () => { + let scheduleds = &ctxt.scheduleds[task]; + quote!( + ptr::write( + #scheduleds.get_mut().get_unchecked_mut(usize::from(index)), + #baseline, + ); + ) + } + #[cfg(not(feature = "timer-queue"))] + () => quote!(), + }; + + let baseline_arg = match () { + #[cfg(feature = "timer-queue")] + () => quote!(#baseline: rtfm::Instant,), + #[cfg(not(feature = "timer-queue"))] + () => quote!(), + }; + + items.push(quote!( + #[inline(always)] + unsafe fn #alias( + #baseline_arg + #priority: &core::cell::Cell<u8>, + #(#args,)* + ) -> Result<(), #ty> { + use core::ptr; + + use rtfm::Mutex; + + if let Some(index) = (#free { #priority }).lock(|f| f.split().1.dequeue()) { + ptr::write(#inputs.get_mut().get_unchecked_mut(usize::from(index)), (#pats)); + #scheduleds_write + + #ready { #priority }.lock(|rq| { + rq.split().0.enqueue_unchecked((#enum_::#task, index)) + }); + + rtfm::pend(#device::Interrupt::#dispatcher); + + Ok(()) + } else { + Err((#pats)) + } + } + )) + } + + // Generate `spawn` structs; these call the `spawn` functions generated above + for (name, spawn) in app.spawn_callers() { + if spawn.is_empty() { + continue; + } + + #[cfg(feature = "timer-queue")] + let is_idle = name.to_string() == "idle"; + + let mut methods = vec![]; + for task in spawn { + let alias = &ctxt.spawn_fn[task]; + let inputs = &app.tasks[task].inputs; + let ty = tuple_ty(inputs); + let pats = tuple_pat(inputs); + + let instant = match () { + #[cfg(feature = "timer-queue")] + () => { + if is_idle { + quote!(rtfm::Instant::now(),) + } else { + quote!(self.#baseline,) + } + } + #[cfg(not(feature = "timer-queue"))] + () => quote!(), + }; + methods.push(quote!( + #[allow(unsafe_code)] + #[inline] + pub fn #task(&self, #(#inputs,)*) -> Result<(), #ty> { + unsafe { #alias(#instant &self.#priority, #pats) } + } + )); + } + + items.push(quote!( + impl<'a> #name::Spawn<'a> { + #(#methods)* + } + )); + } + + quote!(#(#items)*) +} + +#[cfg(feature = "timer-queue")] +fn schedule(ctxt: &Context, app: &App) -> proc_macro2::TokenStream { + let mut items = vec![]; + + // Generate `schedule` functions + let priority = &ctxt.priority; + let timer_queue = &ctxt.timer_queue; + for (task, alias) in &ctxt.schedule_fn { + let free = &ctxt.free_queues[task]; + let enum_ = &ctxt.schedule_enum; + let inputs = &ctxt.inputs[task]; + let scheduleds = &ctxt.scheduleds[task]; + let args = &app.tasks[task].inputs; + let ty = tuple_ty(args); + let pats = tuple_pat(args); + + items.push(quote!( + #[inline(always)] + unsafe fn #alias( + #priority: &core::cell::Cell<u8>, + instant: rtfm::Instant, + #(#args,)* + ) -> Result<(), #ty> { + use core::ptr; + + use rtfm::Mutex; + + if let Some(index) = (#free { #priority }).lock(|f| f.split().1.dequeue()) { + ptr::write(#inputs.get_mut().get_unchecked_mut(usize::from(index)), (#pats)); + ptr::write( + #scheduleds.get_mut().get_unchecked_mut(usize::from(index)), + instant, + ); + + let nr = rtfm::export::NotReady { + instant, + index, + task: #enum_::#task, + }; + + ({#timer_queue { #priority }}).lock(|tq| tq.enqueue_unchecked(nr)); + + Ok(()) + } else { + Err((#pats)) + } + } + )) + } + + // Generate `Schedule` structs; these call the `schedule` functions generated above + for (name, schedule) in app.schedule_callers() { + if schedule.is_empty() { + continue; + } + + debug_assert!(!schedule.is_empty()); + + let mut methods = vec![]; + for task in schedule { + let alias = &ctxt.schedule_fn[task]; + let inputs = &app.tasks[task].inputs; + let ty = tuple_ty(inputs); + let pats = tuple_pat(inputs); + + methods.push(quote!( + #[inline] + pub fn #task( + &self, + instant: rtfm::Instant, + #(#inputs,)* + ) -> Result<(), #ty> { + unsafe { #alias(&self.#priority, instant, #pats) } + } + )); + } + + items.push(quote!( + impl<'a> #name::Schedule<'a> { + #(#methods)* + } + )); + } + + quote!(#(#items)*) +} + +fn timer_queue(ctxt: &Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream { + let tasks = &analysis.timer_queue.tasks; + + if tasks.is_empty() { + return quote!(); + } + + let mut items = vec![]; + + let enum_ = &ctxt.schedule_enum; + items.push(quote!( + #[allow(dead_code)] + #[allow(non_camel_case_types)] + #[derive(Clone, Copy)] + enum #enum_ { #(#tasks,)* } + )); + + let cap = mk_typenum_capacity(analysis.timer_queue.capacity, false); + let tq = &ctxt.timer_queue; + let symbol = format!("TIMER_QUEUE::{}", tq); + items.push(quote!( + #[export_name = #symbol] + static mut #tq: + rtfm::export::MaybeUninit<rtfm::export::TimerQueue<#enum_, #cap>> = + rtfm::export::MaybeUninit::uninitialized(); + )); + + items.push(mk_resource( + ctxt, + tq, + quote!(rtfm::export::TimerQueue<#enum_, #cap>), + analysis.timer_queue.ceiling, + quote!(#tq.get_mut()), + app, + None, + )); + + let priority = &ctxt.priority; + let device = &app.args.device; + let arms = tasks + .iter() + .map(|task| { + let level = app.tasks[task].args.priority; + let tenum = &ctxt.enums[&level]; + let ready = &ctxt.ready_queues[&level]; + let dispatcher = &analysis.dispatchers[&level].interrupt; + + quote!( + #enum_::#task => { + (#ready { #priority }).lock(|rq| { + rq.split().0.enqueue_unchecked((#tenum::#task, index)) + }); + + rtfm::pend(#device::Interrupt::#dispatcher); + } + ) + }) + .collect::<Vec<_>>(); + + let logical_prio = analysis.timer_queue.priority; + items.push(quote!( + #[rtfm::export::exception] + #[doc(hidden)] + unsafe fn SysTick() { + use rtfm::Mutex; + + let ref #priority = core::cell::Cell::new(#logical_prio); + + rtfm::export::run(|| { + rtfm::export::sys_tick(#tq { #priority }, |task, index| { + match task { + #(#arms)* + } + }); + }) + } + )); + + quote!(#(#items)*) +} + +fn pre_init(ctxt: &Context, analysis: &Analysis) -> proc_macro2::TokenStream { + let mut exprs = vec![]; + + // FIXME(MaybeUninit) Because we are using a fake MaybeUninit we need to set the Option tag to + // Some; otherwise the get_ref and get_mut could result in UB. Also heapless collections can't + // be constructed in const context; we have to initialize them at runtime (i.e. here). + + // these are `MaybeUninit` arrays + for inputs in ctxt.inputs.values() { + exprs.push(quote!(#inputs.set(core::mem::uninitialized());)) + } + + #[cfg(feature = "timer-queue")] + for inputs in ctxt.scheduleds.values() { + exprs.push(quote!(#inputs.set(core::mem::uninitialized());)) + } + + // these are `MaybeUninit` `ReadyQueue`s + for queue in ctxt.ready_queues.values() { + exprs.push(quote!(#queue.set(rtfm::export::ReadyQueue::new());)) + } + + // these are `MaybeUninit` `FreeQueue`s + for free in ctxt.free_queues.values() { + exprs.push(quote!(#free.set(rtfm::export::FreeQueue::new());)) + } + + // end-of-FIXME + + // Initialize the timer queue + if !analysis.timer_queue.tasks.is_empty() { + let tq = &ctxt.timer_queue; + exprs.push(quote!(#tq.set(rtfm::export::TimerQueue::new(p.SYST));)); + } + + // Populate the `FreeQueue`s + for (task, alias) in &ctxt.free_queues { + let capacity = analysis.capacities[task]; + exprs.push(quote!( + for i in 0..#capacity { + #alias.get_mut().enqueue_unchecked(i); + } + )) + } + + // Set the cycle count to 0 and disable it while `init` executes + if cfg!(feature = "timer-queue") { + exprs.push(quote!(p.DWT.ctrl.modify(|r| r & !1);)); + exprs.push(quote!(p.DWT.cyccnt.write(0);)); + } + + quote!( + let mut p = rtfm::export::Peripherals::steal(); + #(#exprs)* + ) +} + +fn assertions(app: &App, analysis: &Analysis) -> proc_macro2::TokenStream { + let mut items = vec![]; + + for ty in &analysis.assert_sync { + items.push(quote!(rtfm::export::assert_sync::<#ty>())); + } + + for task in &analysis.tasks_assert_send { + let ty = tuple_ty(&app.tasks[task].inputs); + items.push(quote!(rtfm::export::assert_send::<#ty>())); + } + + // all late resources need to be `Send` + for ty in &analysis.resources_assert_send { + items.push(quote!(rtfm::export::assert_send::<#ty>())); + } + + quote!(#(#items;)*) +} + +fn mk_resource( + ctxt: &Context, + struct_: &Ident, + ty: proc_macro2::TokenStream, + ceiling: u8, + ptr: proc_macro2::TokenStream, + app: &App, + module: Option<&mut Vec<proc_macro2::TokenStream>>, +) -> proc_macro2::TokenStream { + let priority = &ctxt.priority; + let device = &app.args.device; + + let mut items = vec![]; + + let path = if let Some(module) = module { + let doc = format!("`{}`", ty); + module.push(quote!( + #[doc = #doc] + pub struct #struct_<'a> { + #[doc(hidden)] + pub #priority: &'a core::cell::Cell<u8>, + } + )); + + quote!(resources::#struct_) + } else { + items.push(quote!( + struct #struct_<'a> { + #priority: &'a core::cell::Cell<u8>, + } + )); + + quote!(#struct_) + }; + + items.push(quote!( + unsafe impl<'a> rtfm::Mutex for #path<'a> { + const CEILING: u8 = #ceiling; + const NVIC_PRIO_BITS: u8 = #device::NVIC_PRIO_BITS; + type Data = #ty; + + #[inline(always)] + unsafe fn priority(&self) -> &core::cell::Cell<u8> { + &self.#priority + } + + #[inline(always)] + fn ptr(&self) -> *mut Self::Data { + unsafe { #ptr } + } + } + )); + + quote!(#(#items)*) +} + +fn mk_capacity_literal(capacity: u8) -> LitInt { + LitInt::new(u64::from(capacity), IntSuffix::None, Span::call_site()) +} + +fn mk_typenum_capacity(capacity: u8, power_of_two: bool) -> proc_macro2::TokenStream { + let capacity = if power_of_two { + capacity + .checked_next_power_of_two() + .expect("capacity.next_power_of_two()") + } else { + capacity + }; + + let ident = Ident::new(&format!("U{}", capacity), Span::call_site()); + + quote!(rtfm::export::consts::#ident) +} + +fn mk_ident() -> Ident { + static CALL_COUNT: AtomicUsize = AtomicUsize::new(0); + + let elapsed = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + + let secs = elapsed.as_secs(); + let nanos = elapsed.subsec_nanos(); + + let count = CALL_COUNT.fetch_add(1, Ordering::SeqCst) as u32; + let mut seed: [u8; 16] = [0; 16]; + + for (i, v) in seed.iter_mut().take(8).enumerate() { + *v = ((secs >> (i * 8)) & 0xFF) as u8 + } + + for (i, v) in seed.iter_mut().skip(8).take(4).enumerate() { + *v = ((nanos >> (i * 8)) & 0xFF) as u8 + } + + for (i, v) in seed.iter_mut().skip(12).enumerate() { + *v = ((count >> (i * 8)) & 0xFF) as u8 + } + + let mut rng = rand::rngs::SmallRng::from_seed(seed); + Ident::new( + &(0..16) + .map(|i| { + if i == 0 || rng.gen() { + ('a' as u8 + rng.gen::<u8>() % 25) as char + } else { + ('0' as u8 + rng.gen::<u8>() % 10) as char + } + }) + .collect::<String>(), + Span::call_site(), + ) +} + +// `once = true` means that these locals will be called from a function that will run *once* +fn mk_locals(locals: &HashMap<Ident, Static>, once: bool) -> proc_macro2::TokenStream { + let lt = if once { Some(quote!('static)) } else { None }; + + let locals = locals + .iter() + .map(|(name, static_)| { + let attrs = &static_.attrs; + let expr = &static_.expr; + let ident = name; + let ty = &static_.ty; + + quote!( + #[allow(non_snake_case)] + let #ident: &#lt mut #ty = { + #(#attrs)* + static mut #ident: #ty = #expr; + + unsafe { &mut #ident } + }; + ) + }) + .collect::<Vec<_>>(); + + quote!(#(#locals)*) +} + +fn tuple_pat(inputs: &[ArgCaptured]) -> proc_macro2::TokenStream { + if inputs.len() == 1 { + let pat = &inputs[0].pat; + quote!(#pat) + } else { + let pats = inputs.iter().map(|i| &i.pat).collect::<Vec<_>>(); + + quote!(#(#pats,)*) + } +} + +fn tuple_ty(inputs: &[ArgCaptured]) -> proc_macro2::TokenStream { + if inputs.len() == 1 { + let ty = &inputs[0].ty; + quote!(#ty) + } else { + let tys = inputs.iter().map(|i| &i.ty).collect::<Vec<_>>(); + + quote!((#(#tys,)*)) + } +} + +#[derive(Clone, Eq, Hash, PartialEq)] +enum Kind { + Exception(Ident), + Idle, + Init, + Interrupt(Ident), + Task(Ident), +} + +impl Kind { + fn ident(&self) -> Ident { + match self { + Kind::Init => Ident::new("init", Span::call_site()), + Kind::Idle => Ident::new("idle", Span::call_site()), + Kind::Task(name) | Kind::Interrupt(name) | Kind::Exception(name) => name.clone(), + } + } + + fn is_idle(&self) -> bool { + *self == Kind::Idle + } + + fn is_init(&self) -> bool { + *self == Kind::Init + } + + fn runs_once(&self) -> bool { + match *self { + Kind::Init | Kind::Idle => true, + _ => false, + } + } +} diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 65d5ad89..e382b410 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -1,185 +1,312 @@ -//! Procedural macros of the `cortex-m-rtfm` crate // #![deny(warnings)] #![recursion_limit = "128"] -#[macro_use] -extern crate failure; extern crate proc_macro; extern crate proc_macro2; -extern crate syn; -#[macro_use] extern crate quote; -extern crate rtfm_syntax as syntax; +extern crate rand; +extern crate syn; use proc_macro::TokenStream; -use syntax::{App, Result}; +use syn::parse_macro_input; mod analyze; mod check; -mod trans; +mod codegen; +mod syntax; -/// The `app!` macro, a macro used to specify the tasks and resources of a RTFM application. +/// Attribute used to declare a RTFM application /// -/// The contents of this macro uses a `key: value` syntax. All the possible keys are shown below: +/// 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, +/// like functions and `static` variables. /// -/// ``` text -/// app! { -/// device: .., +/// The `app` attribute has one mandatory argument: /// -/// resources: { .. }, +/// - `device = <path>`. The path must point to a device crate generated using [`svd2rust`] +/// **v0.14.x**. /// -/// init: { .. }, +/// [`svd2rust`]: https://crates.io/crates/svd2rust /// -/// idle: { .. }, +/// The items allowed in the block value of the `const` item are specified below: /// -/// tasks: { .. }, -/// } -/// ``` +/// # 1. `static [mut]` variables /// -/// # `device` +/// 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. /// -/// The value of this key is a Rust path, like `foo::bar::baz`, that must point to a *device crate*, -/// a crate generated using `svd2rust`. +/// [`lock`]: ../rtfm/trait.Mutex.html#method.lock /// -/// # `resources` +/// `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. /// -/// This key is optional. Its value is a list of `static` variables. These variables are the data -/// that can be safely accessed, modified and shared by tasks. +/// [`Send`]: https://doc.rust-lang.org/core/marker/trait.Send.html +/// [`Sync`]: https://doc.rust-lang.org/core/marker/trait.Sync.html /// -/// ``` text -/// resources: { -/// static A: bool = false; -/// static B: i32 = 0; -/// static C: [u8; 16] = [0; 16]; -/// static D: Thing = Thing::new(..); -/// static E: Thing; -/// } -/// ``` +/// 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 initial value of a resource can be omitted. This means that the resource will be runtime -/// initialized; these runtime initialized resources are also known as *late resources*. +/// 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. /// -/// If this key is omitted its value defaults to an empty list. +/// [`Mutex`]: ../rtfm/trait.Mutex.html /// -/// # `init` +/// # 2. `fn` /// -/// This key is optional. Its value is a set of key values. All the possible keys are shown below: +/// 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. /// -/// ``` text -/// init: { -/// path: .., -/// } -/// ``` +/// ## a. `#[init]` /// -/// ## `init.path` +/// 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 ()`. /// -/// This key is optional. Its value is a Rust path, like `foo::bar::baz`, that points to the -/// initialization function. +/// The `init` function runs after memory (RAM) is initialized and runs with interrupts disabled. +/// Interrupts are re-enabled after `init` returns. /// -/// If the key is omitted its value defaults to `init`. +/// The `init` attribute accepts the following optional arguments: /// -/// ## `init.resources` +/// - `resources = [RESOURCE_A, RESOURCE_B, ..]`. This is the list of resources this function has +/// access to. /// -/// This key is optional. Its value is a set of resources the `init` function *owns*. The resources -/// in this list must be a subset of the resources listed in the top `resources` key. Note that some -/// restrictions apply: +/// - `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. /// -/// - The resources in this list can't be late resources. -/// - The resources that appear in this list can't appear in other list like `idle.resources` or -/// `tasks.$TASK.resources` +/// - `spawn = [task_a, task_b, ..]`. This is the list of *software* tasks that this function can +/// immediately spawn. /// -/// If this key is omitted its value is assumed to be an empty list. +/// The `app` attribute will injected a *context* into this function that comprises the following +/// variables: /// -/// # `idle` +/// - `core: rtfm::Peripherals`. Exclusive access to core peripherals. See [`rtfm::Peripherals`] for +/// more details. /// -/// This key is optional. Its value is a set of key values. All the possible keys are shown below: +/// [`rtfm::Peripherals`]: ../rtfm/struct.Peripherals.html /// -/// ``` text -/// idle: { -/// path: .., -/// resources: [..], -/// } -/// ``` +/// - `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. /// -/// ## `idle.path` +/// - `start: rtfm::Instant`. The `start` time of the system: `Instant(0 /* cycles */)`. **NOTE**: +/// only present if the `timer-queue` feature is enabled. /// -/// This key is optional. Its value is a Rust path, like `foo::bar::baz`, that points to the idle -/// loop function. +/// - `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`). /// -/// If the key is omitted its value defaults to `idle`. +/// - `schedule: init::Schedule`. A `struct` that can be used to schedule *software* tasks. +/// **NOTE**: only present if the `timer-queue` feature is enabled. /// -/// ## `idle.resources` +/// - `spawn: init::Spawn`. A `struct` that can be used to spawn *software* tasks. /// -/// This key is optional. Its value is a list of resources the `idle` loop has access to. The -/// resources in this list must be a subset of the resources listed in the top `resources` key. +/// Other properties / constraints: /// -/// If omitted its value defaults to an empty list. +/// - The `init` function can **not** be called from software. /// -/// # `tasks` +/// - 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`. /// -/// This key is optional. Its value is a list of tasks. Each task itself is a set of key value pair. -/// The full syntax is shown below: +/// - Assignments (e.g. `FOO = 0`) at the end of this function can be used to initialize *late* +/// resources. /// -/// ``` text -/// tasks: { -/// $TASK: { -/// enabled: .., -/// path: .., -/// priority: .., -/// resources: [..], -/// }, -/// } -/// ``` +/// ## b. `#[idle]` /// -/// If this key is omitted its value is assumed to be an empty list. +/// 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() -> !`. /// -/// ## `tasks.$TASK` +/// 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 +/// [SLEEPONEXIT] bit after executing `init`. /// -/// The key must be either a Cortex-M exception or a device specific interrupt. `PENDSV`, `SVCALL`, -/// `SYS_TICK` are considered as exceptions. All other names are assumed to be interrupts. +/// [SLEEPONEXIT]: https://developer.arm.com/products/architecture/cpu-architecture/m-profile/docs/100737/0100/power-management/sleep-mode/sleep-on-exit-bit /// -/// ## `tasks.$TASK.enabled` +/// The `idle` attribute accepts the following optional arguments: /// -/// This key is optional for interrupts and forbidden for exceptions. Its value must be a boolean -/// and indicates whether the interrupt will be enabled (`true`) or disabled (`false`) after `init` -/// ends and before `idle` starts. +/// - `resources = (..)`. Same meaning / function as [`#[init].resources`](#a-init). /// -/// If this key is omitted its value defaults to `true`. +/// - `schedule = (..)`. Same meaning / function as [`#[init].schedule`](#a-init). /// -/// ## `tasks.$TASK.path` +/// - `spawn = (..)`. Same meaning / function as [`#[init].spawn`](#a-init). /// -/// The value of this key is a Rust path, like `foo::bar::baz`, that points to the handler of this -/// task. +/// The `app` attribute will injected a *context* into this function that comprises the following +/// variables: /// -/// ## `tasks.$TASK.priority` +/// - `resources: _`. Same meaning / function as [`init.resources`](#a-init). /// -/// This key is optional. Its value is an integer with type `u8` that specifies the priority of this -/// task. The minimum valid priority is 1. The maximum valid priority depends on the number of the -/// NVIC priority bits the device has; if the device has 4 priority bits the maximum allowed value -/// would be 16. +/// - `schedule: idle::Schedule`. Same meaning / function as [`init.schedule`](#a-init). /// -/// If this key is omitted its value defaults to `1`. +/// - `spawn: idle::Spawn`. Same meaning / function as [`init.spawn`](#a-init). /// -/// ## `tasks.$TASK.resources` +/// Other properties / constraints: /// -/// This key is optional. Its value is a list of resources this task has access to. The resources in -/// this list must be a subset of the resources listed in the top `resources` key. +/// - The `idle` function can **not** be called from software. /// -/// If omitted its value defaults to an empty list. -#[proc_macro] -pub fn app(ts: TokenStream) -> TokenStream { - match run(ts) { - Err(e) => panic!("error: {}", e), - Ok(ts) => ts, - } -} +/// - 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()`. +/// +/// The name of the function must match one of the Cortex-M exceptions that has [configurable +/// priority][system-handler]. +/// +/// [system-handler]: ../cortex_m/peripheral/scb/enum.SystemHandler.html +/// +/// The `exception` attribute accepts the following optional arguments. +/// +/// - `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 +/// is assumed to be 1. +/// +/// - `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`. The time at which this handler started executing. **NOTE**: only +/// present if the `timer-queue` feature is enabled. +/// +/// - `resources: _`. Same meaning / function as [`init.resources`](#a-init). +/// +/// - `schedule: <exception-name>::Schedule`. Same meaning / function as [`init.schedule`](#a-init). +/// +/// - `spawn: <exception-name>::Spawn`. Same meaning / function as [`init.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`. +/// +/// # 3. `extern` block +/// +/// This `extern` block contains a list of interrupts which are *not* used by the application as +/// hardware tasks. These interrupts will be used to dispatch software tasks. Each interrupt will be +/// used to dispatch *multiple* software tasks *at the same priority level*. +/// +/// 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. +#[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 app = match syntax::App::parse(items, args) { + Err(e) => return e.to_compile_error().into(), + Ok(app) => app, + }; -fn run(ts: TokenStream) -> Result<TokenStream> { - let app = App::parse(ts)?.check()?; - let app = check::app(app)?; + // Check the specification + if let Err(e) = check::app(&app) { + return e.to_compile_error().into(); + } - let ownerships = analyze::app(&app); - let tokens = trans::app(&app, &ownerships); + // Ceiling analysis + let analysis = analyze::app(&app); - Ok(tokens.into()) + // Code generation + codegen::app(&app, &analysis) } diff --git a/macros/src/syntax.rs b/macros/src/syntax.rs new file mode 100644 index 00000000..24586dcf --- /dev/null +++ b/macros/src/syntax.rs @@ -0,0 +1,1235 @@ +use std::{ + collections::{HashMap, HashSet}, + 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 = HashMap::new(); + let mut interrupts = HashMap::new(); + let mut resources = HashMap::new(); + let mut tasks = HashMap::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<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<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<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<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<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 = HashSet<Ident>; + +pub type Exceptions = HashMap<Ident, Exception>; + +pub type Interrupts = HashMap<Ident, Interrupt>; + +pub type Resources = HashMap<Ident, Resource>; + +pub type Statics = Vec<ItemStatic>; + +pub type Tasks = HashMap<Ident, Task>; + +pub type FreeInterrupts = HashMap<Ident, FreeInterrupt>; + +pub struct Idle { + pub args: IdleArgs, + pub attrs: Vec<Attribute>, + pub unsafety: Option<Token![unsafe]>, + pub statics: HashMap<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()), + }) + } +} + +pub struct Assign { + pub left: Ident, + pub right: Box<Expr>, +} + +pub struct Init { + pub args: InitArgs, + pub attrs: Vec<Attribute>, + pub unsafety: Option<Token![unsafe]>, + pub statics: HashMap<Ident, Static>, + pub stmts: Vec<Stmt>, + pub assigns: Vec<Assign>, +} + +impl Init { + fn check(args: InitArgs, 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, + "`init` must have type signature `[unsafe] fn()`", + )); + } + + let (statics, stmts) = extract_statics(item.block.stmts); + let (stmts, assigns) = extract_assignments(stmts); + + Ok(Init { + args, + attrs: item.attrs, + unsafety: item.unsafety, + statics: Static::parse(statics)?, + stmts, + assigns, + }) + } +} + +pub struct Exception { + pub args: ExceptionArgs, + pub attrs: Vec<Attribute>, + pub unsafety: Option<Token![unsafe]>, + pub statics: Statics, + pub stmts: Vec<Stmt>, +} + +pub struct ExceptionArgs { + pub priority: u8, + pub resources: Idents, + pub schedule: Idents, + pub spawn: Idents, +} + +impl Parse for ExceptionArgs { + fn parse(input: ParseStream) -> parse::Result<Self> { + parse_args(input, false).map( + |TaskArgs { + priority, + resources, + schedule, + spawn, + .. + }| { + ExceptionArgs { + 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 &*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, + stmts, + }) + } +} + +pub struct Interrupt { + pub args: InterruptArgs, + pub attrs: Vec<Attribute>, + pub unsafety: Option<Token![unsafe]>, + pub statics: Statics, + 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, + stmts, + }) + } +} + +pub struct Resource { + pub singleton: bool, + 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())), + ); + } + + Ok(Resource { + singleton: pos.is_some(), + attrs: item.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 Default for TaskArgs { + fn default() -> Self { + TaskArgs { + capacity: None, + priority: 1, + resources: Idents::new(), + schedule: Idents::new(), + spawn: Idents::new(), + } + } +} + +impl Parse for TaskArgs { + fn parse(input: ParseStream) -> parse::Result<Self> { + parse_args(input, true) + } +} + +// Parser shared by TaskArgs and ExceptionArgs / InterruptArgs +fn parse_args(input: ParseStream, accept_capacity: bool) -> parse::Result<TaskArgs> { + if input.is_empty() { + return Ok(TaskArgs::default()); + } + + 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 { + "capacity" if accept_capacity => { + // #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" => { + // #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) { + return Err(parse::Error::new( + lit.span(), + "this literal must be in the range 0...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(), + "expected one of: priority, resources, schedule or spawn", + )) + } + } + + if content.is_empty() { + break; + } + + // , + let _: Token![,] = content.parse()?; + } + + Ok(TaskArgs { + 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 { + pub attrs: Vec<Attribute>, + pub ty: Box<Type>, + pub expr: Box<Expr>, +} + +impl Static { + fn parse(items: Vec<ItemStatic>) -> parse::Result<HashMap<Ident, Static>> { + let mut statics = HashMap::new(); + + for item in items { + if statics.contains_key(&item.ident) { + return Err(parse::Error::new( + item.ident.span(), + "this `static` is listed twice", + )); + } + + statics.insert( + item.ident, + Static { + attrs: item.attrs, + ty: item.ty, + expr: item.expr, + }, + ); + } + + Ok(statics) + } +} + +pub struct Task { + pub args: TaskArgs, + pub attrs: Vec<Attribute>, + pub unsafety: Option<Token![unsafe]>, + pub inputs: Vec<ArgCaptured>, + pub statics: HashMap<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`", + )); + } + _ => {} + } + + Ok(Task { + args, + attrs: item.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 + } +} + +/// 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) +} + +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 { + 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/trans.rs b/macros/src/trans.rs deleted file mode 100644 index dcd6cfb6..00000000 --- a/macros/src/trans.rs +++ /dev/null @@ -1,631 +0,0 @@ -use proc_macro2::{TokenStream, Span}; -use syn::{Ident, LitStr}; - -use analyze::Ownerships; -use check::{App, Kind}; - -fn krate() -> Ident { - Ident::new("rtfm", Span::call_site()) -} - -pub fn app(app: &App, ownerships: &Ownerships) -> TokenStream { - let mut root = vec![]; - let mut main = vec![quote!(#![allow(path_statements)])]; - - ::trans::tasks(app, ownerships, &mut root, &mut main); - ::trans::init(app, &mut main, &mut root); - ::trans::idle(app, ownerships, &mut main, &mut root); - ::trans::resources(app, ownerships, &mut root); - - root.push(quote! { - #[allow(unsafe_code)] - fn main() { - #(#main)* - } - }); - - quote!(#(#root)*) -} - -fn idle(app: &App, ownerships: &Ownerships, main: &mut Vec<TokenStream>, root: &mut Vec<TokenStream>) { - let krate = krate(); - - let mut mod_items = vec![]; - let mut tys = vec![]; - let mut exprs = vec![]; - - if !app.idle.resources.is_empty() { - tys.push(quote!(&mut #krate::Threshold)); - exprs.push(quote!(unsafe { &mut #krate::Threshold::new(0) })); - } - - if !app.idle.resources.is_empty() { - let mut needs_reexport = false; - for name in &app.idle.resources { - if ownerships[name].is_owned() { - if app.resources.get(name).is_some() { - needs_reexport = true; - break; - } - } - } - - let super_ = if needs_reexport { - None - } else { - Some(Ident::new("super", Span::call_site())) - }; - let mut rexprs = vec![]; - let mut rfields = vec![]; - for name in &app.idle.resources { - if ownerships[name].is_owned() { - let resource = app.resources.get(name).expect(&format!( - "BUG: resource {} assigned to `idle` has no definition", - name - )); - let ty = &resource.ty; - - rfields.push(quote! { - pub #name: &'static mut #ty, - }); - - let _name = Ident::new(&name.to_string(), Span::call_site()); - rexprs.push(if resource.expr.is_some() { - quote! { - #name: &mut #super_::#_name, - } - } else { - quote! { - #name: #super_::#_name.as_mut(), - } - }); - } else { - rfields.push(quote! { - pub #name: ::idle::#name, - }); - - rexprs.push(quote! { - #name: ::idle::#name { _0: ::core::marker::PhantomData }, - }); - } - } - - if needs_reexport { - root.push(quote! { - #[allow(non_camel_case_types)] - #[allow(non_snake_case)] - pub struct _idleResources { - #(#rfields)* - } - }); - - mod_items.push(quote! { - pub use ::_idleResources as Resources; - }); - } else { - mod_items.push(quote! { - #[allow(non_snake_case)] - pub struct Resources { - #(#rfields)* - } - }); - } - - mod_items.push(quote! { - #[allow(unsafe_code)] - impl Resources { - pub unsafe fn new() -> Self { - Resources { - #(#rexprs)* - } - } - } - }); - - tys.push(quote!(idle::Resources)); - exprs.push(quote!(unsafe { idle::Resources::new() })); - } - - let device = &app.device; - for name in &app.idle.resources { - let ceiling = ownerships[name].ceiling(); - - // owned resource - if ceiling == 0 { - continue; - } - - let _name = Ident::new(&name.to_string(), Span::call_site()); - let resource = app.resources - .get(name) - .expect(&format!("BUG: resource {} has no definition", name)); - - let ty = &resource.ty; - let _static = if resource.expr.is_some() { - quote!(#_name) - } else { - quote!(#_name.some) - }; - - mod_items.push(quote! { - #[allow(non_camel_case_types)] - pub struct #name { _0: ::core::marker::PhantomData<*const ()> } - }); - - root.push(quote! { - #[allow(unsafe_code)] - unsafe impl #krate::Resource for idle::#name { - type Data = #ty; - - fn borrow<'cs>(&'cs self, t: &'cs Threshold) -> &'cs Self::Data { - assert!(t.value() >= #ceiling); - - unsafe { &#_static } - } - - fn borrow_mut<'cs>( - &'cs mut self, - t: &'cs Threshold, - ) -> &'cs mut Self::Data { - assert!(t.value() >= #ceiling); - - unsafe { &mut #_static } - } - - fn claim<R, F>(&self, t: &mut Threshold, f: F) -> R - where - F: FnOnce(&Self::Data, &mut Threshold) -> R - { - unsafe { - #krate::claim( - &#_static, - #ceiling, - #device::NVIC_PRIO_BITS, - t, - f, - ) - } - } - - fn claim_mut<R, F>(&mut self, t: &mut Threshold, f: F) -> R - where - F: FnOnce(&mut Self::Data, &mut Threshold) -> R - { - unsafe { - #krate::claim( - &mut #_static, - #ceiling, - #device::NVIC_PRIO_BITS, - t, - f, - ) - } - } - } - }); - } - - if !mod_items.is_empty() { - root.push(quote! { - #[allow(unsafe_code)] - mod idle { - #(#mod_items)* - } - }); - } - - let idle = &app.idle.path; - main.push(quote! { - // type check - let idle: fn(#(#tys),*) -> ! = #idle; - - idle(#(#exprs),*); - }); -} - -fn init(app: &App, main: &mut Vec<TokenStream>, root: &mut Vec<TokenStream>) { - let device = &app.device; - let krate = krate(); - - let mut tys = vec![quote!(init::Peripherals)]; - let mut exprs = vec![ - quote!{ - init::Peripherals { - core: ::#device::CorePeripherals::steal(), - device: ::#device::Peripherals::steal(), - } - }, - ]; - let mut ret = None; - let mut mod_items = vec![]; - - let (init_resources, late_resources): (Vec<_>, Vec<_>) = app.resources - .iter() - .partition(|&(_, res)| res.expr.is_some()); - - if !init_resources.is_empty() { - let mut fields = vec![]; - let mut lifetime = None; - let mut rexprs = vec![]; - - for (name, resource) in init_resources { - let ty = &resource.ty; - - if app.init.resources.contains(name) { - fields.push(quote! { - pub #name: &'static mut #ty, - }); - - let expr = &resource.expr; - rexprs.push(quote!(#name: { - static mut #name: #ty = #expr; - &mut #name - },)); - } else { - let _name = Ident::new(&name.to_string(), Span::call_site()); - lifetime = Some(quote!('a)); - - fields.push(quote! { - pub #name: &'a mut #ty, - }); - - rexprs.push(quote! { - #name: &mut ::#_name, - }); - } - } - - root.push(quote! { - #[allow(non_camel_case_types)] - #[allow(non_snake_case)] - pub struct _initResources<#lifetime> { - #(#fields)* - } - }); - - mod_items.push(quote! { - pub use ::_initResources as Resources; - - #[allow(unsafe_code)] - impl<#lifetime> Resources<#lifetime> { - pub unsafe fn new() -> Self { - Resources { - #(#rexprs)* - } - } - } - }); - - tys.push(quote!(init::Resources)); - exprs.push(quote!(init::Resources::new())); - } - - // Initialization statements for late resources - let mut late_resource_init = vec![]; - - if !late_resources.is_empty() { - // `init` must initialize and return resources - - let mut fields = vec![]; - - for (name, resource) in late_resources { - let _name = Ident::new(&name.to_string(), Span::call_site()); - - let ty = &resource.ty; - - fields.push(quote! { - pub #name: #ty, - }); - - late_resource_init.push(quote! { - #_name = #krate::UntaggedOption { some: _late_resources.#name }; - }); - } - - root.push(quote! { - #[allow(non_camel_case_types)] - #[allow(non_snake_case)] - pub struct _initLateResources { - #(#fields)* - } - }); - - mod_items.push(quote! { - pub use ::_initLateResources as LateResources; - }); - - // `init` must return the initialized resources - ret = Some(quote!( -> ::init::LateResources)); - } - - root.push(quote! { - #[allow(unsafe_code)] - mod init { - pub struct Peripherals { - pub core: ::#device::CorePeripherals, - pub device: ::#device::Peripherals, - } - - #(#mod_items)* - } - }); - - let mut exceptions = vec![]; - let mut interrupts = vec![]; - for (name, task) in &app.tasks { - match task.kind { - Kind::Exception(ref e) => { - if exceptions.is_empty() { - exceptions.push(quote! { - let scb = &*#device::SCB::ptr(); - }); - } - - let nr = e.nr(); - let priority = task.priority; - exceptions.push(quote! { - let prio_bits = #device::NVIC_PRIO_BITS; - let hw = ((1 << prio_bits) - #priority) << (8 - prio_bits); - scb.shpr[#nr - 4].write(hw); - }); - } - Kind::Interrupt { enabled } => { - // Interrupt. These are enabled / disabled through the NVIC - if interrupts.is_empty() { - interrupts.push(quote! { - use #device::Interrupt; - - let mut nvic: #device::NVIC = core::mem::transmute(()); - }); - } - - let priority = task.priority; - interrupts.push(quote! { - let prio_bits = #device::NVIC_PRIO_BITS; - let hw = ((1 << prio_bits) - #priority) << (8 - prio_bits); - nvic.set_priority(Interrupt::#name, hw); - }); - - if enabled { - interrupts.push(quote! { - nvic.enable(Interrupt::#name); - }); - } else { - interrupts.push(quote! { - nvic.disable(Interrupt::#name); - }); - } - } - } - } - - let init = &app.init.path; - main.push(quote! { - // type check - let init: fn(#(#tys,)*) #ret = #init; - - #krate::atomic(unsafe { &mut #krate::Threshold::new(0) }, |_t| unsafe { - let _late_resources = init(#(#exprs,)*); - #(#late_resource_init)* - - #(#exceptions)* - #(#interrupts)* - }); - }); -} - -fn resources(app: &App, ownerships: &Ownerships, root: &mut Vec<TokenStream>) { - let krate = krate(); - - for name in ownerships.keys() { - let _name = Ident::new(&name.to_string(), Span::call_site()); - - // Declare the static that holds the resource - let resource = app.resources - .get(name) - .expect(&format!("BUG: resource {} has no definition", name)); - - let expr = &resource.expr; - let ty = &resource.ty; - - root.push(match *expr { - Some(ref expr) => quote! { - static mut #_name: #ty = #expr; - }, - None => quote! { - // Resource initialized in `init` - static mut #_name: #krate::UntaggedOption<#ty> = - #krate::UntaggedOption { none: () }; - }, - }); - } -} - -fn tasks(app: &App, ownerships: &Ownerships, root: &mut Vec<TokenStream>, main: &mut Vec<TokenStream>) { - let device = &app.device; - let krate = krate(); - - for (tname, task) in &app.tasks { - let mut exprs = vec![]; - let mut fields = vec![]; - let mut items = vec![]; - - let has_resources = !task.resources.is_empty(); - - if has_resources { - for rname in &task.resources { - let ceiling = ownerships[rname].ceiling(); - let _rname = Ident::new(&rname.to_string(), Span::call_site()); - let resource = app.resources - .get(rname) - .expect(&format!("BUG: resource {} has no definition", rname)); - - let ty = &resource.ty; - let _static = if resource.expr.is_some() { - quote!(#_rname) - } else { - quote!(#_rname.some) - }; - - items.push(quote! { - #[allow(non_camel_case_types)] - pub struct #rname { _0: PhantomData<*const ()> } - }); - - root.push(quote! { - #[allow(unsafe_code)] - unsafe impl #krate::Resource for #tname::#rname { - type Data = #ty; - - fn borrow<'cs>(&'cs self, t: &'cs Threshold) -> &'cs Self::Data { - assert!(t.value() >= #ceiling); - - unsafe { &#_static } - } - - fn borrow_mut<'cs>( - &'cs mut self, - t: &'cs Threshold, - ) -> &'cs mut Self::Data { - assert!(t.value() >= #ceiling); - - unsafe { &mut #_static } - } - - fn claim<R, F>(&self, t: &mut Threshold, f: F) -> R - where - F: FnOnce(&Self::Data, &mut Threshold) -> R - { - unsafe { - #krate::claim( - &#_static, - #ceiling, - #device::NVIC_PRIO_BITS, - t, - f, - ) - } - } - - fn claim_mut<R, F>(&mut self, t: &mut Threshold, f: F) -> R - where - F: FnOnce(&mut Self::Data, &mut Threshold) -> R - { - unsafe { - #krate::claim( - &mut #_static, - #ceiling, - #device::NVIC_PRIO_BITS, - t, - f, - ) - } - } - } - }); - - if ceiling <= task.priority { - root.push(quote! { - #[allow(unsafe_code)] - impl core::ops::Deref for #tname::#rname { - type Target = #ty; - - fn deref(&self) -> &Self::Target { - unsafe { &#_static } - } - } - - #[allow(unsafe_code)] - impl core::ops::DerefMut for #tname::#rname { - fn deref_mut(&mut self) -> &mut Self::Target { - unsafe { &mut #_static } - } - } - }) - } - - fields.push(quote! { - pub #rname: #rname, - }); - - exprs.push(quote! { - #rname: #rname { _0: PhantomData }, - }); - } - - items.push(quote! { - #[allow(non_snake_case)] - pub struct Resources { - #(#fields)* - } - }); - - items.push(quote! { - #[allow(unsafe_code)] - impl Resources { - pub unsafe fn new() -> Self { - Resources { - #(#exprs)* - } - } - } - }); - } - - let mut tys = vec![]; - let mut exprs = vec![]; - - let priority = task.priority; - if has_resources { - tys.push(quote!(&mut #krate::Threshold)); - exprs.push(quote! { - &mut if #priority == 1 << #device::NVIC_PRIO_BITS { - #krate::Threshold::new(::core::u8::MAX) - } else { - #krate::Threshold::new(#priority) - } - }); - } - - if has_resources { - tys.push(quote!(#tname::Resources)); - exprs.push(quote!(#tname::Resources::new())); - } - - let path = &task.path; - let _tname = Ident::new(&tname.to_string(), Span::call_site()); - let export_name = LitStr::new(&tname.to_string(), Span::call_site()); - root.push(quote! { - #[allow(non_snake_case)] - #[allow(unsafe_code)] - #[export_name = #export_name] - pub unsafe extern "C" fn #_tname() { - let f: fn(#(#tys,)*) = #path; - - f(#(#exprs,)*) - } - }); - - root.push(quote!{ - #[allow(non_snake_case)] - #[allow(unsafe_code)] - mod #tname { - #[allow(unused_imports)] - use core::marker::PhantomData; - - #[allow(dead_code)] - #[deny(const_err)] - pub const CHECK_PRIORITY: (u8, u8) = ( - #priority - 1, - (1 << ::#device::NVIC_PRIO_BITS) - #priority, - ); - - #(#items)* - } - }); - - // after miri landed (?) rustc won't analyze `const` items unless they are used so we force - // evaluation with this path statement - main.push(quote!(#tname::CHECK_PRIORITY;)); - } -} |