aboutsummaryrefslogtreecommitdiff
path: root/macros/src
diff options
context:
space:
mode:
Diffstat (limited to 'macros/src')
-rw-r--r--macros/src/analyze.rs262
-rw-r--r--macros/src/check.rs182
-rw-r--r--macros/src/codegen.rs1815
-rw-r--r--macros/src/lib.rs367
-rw-r--r--macros/src/syntax.rs1235
-rw-r--r--macros/src/trans.rs631
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;));
- }
-}