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