aboutsummaryrefslogtreecommitdiff
path: root/macros/src/check.rs
diff options
context:
space:
mode:
Diffstat (limited to 'macros/src/check.rs')
-rw-r--r--macros/src/check.rs471
1 files changed, 130 insertions, 341 deletions
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]`",
+ ))
+ }
}