aboutsummaryrefslogtreecommitdiff
path: root/macros/src
diff options
context:
space:
mode:
authorGravatar Emil Fresk <emil.fresk@gmail.com> 2022-12-31 14:45:13 +0100
committerGravatar Henrik Tjäder <henrik@tjaders.com> 2023-03-01 00:29:10 +0100
commit7614b96fe45240dafe91ae549e712b560e2d4c10 (patch)
tree30e1666c0aac09480e1a87e5fe7965487f4a99e6 /macros/src
parent1c5db277e4161470136dbd2a11e914ff1d383581 (diff)
downloadrtic-7614b96fe45240dafe91ae549e712b560e2d4c10.tar.gz
rtic-7614b96fe45240dafe91ae549e712b560e2d4c10.tar.zst
rtic-7614b96fe45240dafe91ae549e712b560e2d4c10.zip
RTIC v2: Initial commit
rtic-syntax is now part of RTIC repository
Diffstat (limited to 'macros/src')
-rw-r--r--macros/src/analyze.rs42
-rw-r--r--macros/src/bindings.rs0
-rw-r--r--macros/src/codegen.rs97
-rw-r--r--macros/src/codegen/assertions.rs10
-rw-r--r--macros/src/codegen/async_dispatchers.rs129
-rw-r--r--macros/src/codegen/dispatchers.rs67
-rw-r--r--macros/src/codegen/hardware_tasks.rs16
-rw-r--r--macros/src/codegen/idle.rs15
-rw-r--r--macros/src/codegen/init.rs16
-rw-r--r--macros/src/codegen/local_resources.rs6
-rw-r--r--macros/src/codegen/local_resources_struct.rs19
-rw-r--r--macros/src/codegen/module.rs263
-rw-r--r--macros/src/codegen/monotonic.rs280
-rw-r--r--macros/src/codegen/post_init.rs17
-rw-r--r--macros/src/codegen/pre_init.rs22
-rw-r--r--macros/src/codegen/shared_resources.rs18
-rw-r--r--macros/src/codegen/shared_resources_struct.rs40
-rw-r--r--macros/src/codegen/software_tasks.rs132
-rw-r--r--macros/src/codegen/timer_queue.rs33
-rw-r--r--macros/src/codegen/util.rs43
-rw-r--r--macros/src/lib.rs84
-rw-r--r--macros/src/syntax.rs158
-rw-r--r--macros/src/syntax/.github/bors.toml3
-rw-r--r--macros/src/syntax/.github/workflows/build.yml213
-rw-r--r--macros/src/syntax/.github/workflows/changelog.yml28
-rw-r--r--macros/src/syntax/.github/workflows/properties/build.properties.json6
-rw-r--r--macros/src/syntax/.gitignore4
-rw-r--r--macros/src/syntax/.travis.yml31
-rw-r--r--macros/src/syntax/accessors.rs113
-rw-r--r--macros/src/syntax/analyze.rs448
-rw-r--r--macros/src/syntax/ast.rs380
-rw-r--r--macros/src/syntax/check.rs66
-rw-r--r--macros/src/syntax/optimize.rs36
-rw-r--r--macros/src/syntax/parse.rs520
-rw-r--r--macros/src/syntax/parse/app.rs539
-rw-r--r--macros/src/syntax/parse/hardware_task.rs96
-rw-r--r--macros/src/syntax/parse/idle.rs45
-rw-r--r--macros/src/syntax/parse/init.rs52
-rw-r--r--macros/src/syntax/parse/monotonic.rs42
-rw-r--r--macros/src/syntax/parse/resource.rs55
-rw-r--r--macros/src/syntax/parse/software_task.rs86
-rw-r--r--macros/src/syntax/parse/util.rs338
42 files changed, 4141 insertions, 467 deletions
diff --git a/macros/src/analyze.rs b/macros/src/analyze.rs
index d255b7f5..ec12cfb4 100644
--- a/macros/src/analyze.rs
+++ b/macros/src/analyze.rs
@@ -1,17 +1,17 @@
use core::ops;
use std::collections::{BTreeMap, BTreeSet};
-use rtic_syntax::{
+use crate::syntax::{
analyze::{self, Priority},
- ast::{App, ExternInterrupt},
- P,
+ ast::{App, Dispatcher},
};
use syn::Ident;
/// Extend the upstream `Analysis` struct with our field
pub struct Analysis {
- parent: P<analyze::Analysis>,
- pub interrupts: BTreeMap<Priority, (Ident, ExternInterrupt)>,
+ parent: analyze::Analysis,
+ pub interrupts_normal: BTreeMap<Priority, (Ident, Dispatcher)>,
+ pub interrupts_async: BTreeMap<Priority, (Ident, Dispatcher)>,
}
impl ops::Deref for Analysis {
@@ -23,25 +23,43 @@ impl ops::Deref for Analysis {
}
// Assign an interrupt to each priority level
-pub fn app(analysis: P<analyze::Analysis>, app: &App) -> P<Analysis> {
+pub fn app(analysis: analyze::Analysis, app: &App) -> Analysis {
+ let mut available_interrupt = app.args.dispatchers.clone();
+
// the set of priorities (each priority only once)
let priorities = app
.software_tasks
.values()
+ .filter(|task| !task.is_async)
+ .map(|task| task.args.priority)
+ .collect::<BTreeSet<_>>();
+
+ let priorities_async = app
+ .software_tasks
+ .values()
+ .filter(|task| task.is_async)
.map(|task| task.args.priority)
.collect::<BTreeSet<_>>();
// map from priorities to interrupts (holding name and attributes)
- let interrupts: BTreeMap<Priority, _> = priorities
+
+ let interrupts_normal: BTreeMap<Priority, _> = priorities
.iter()
.copied()
.rev()
- .zip(&app.args.extern_interrupts)
- .map(|(p, (id, ext))| (p, (id.clone(), ext.clone())))
+ .map(|p| (p, available_interrupt.pop().expect("UNREACHABLE")))
.collect();
- P::new(Analysis {
+ let interrupts_async: BTreeMap<Priority, _> = priorities_async
+ .iter()
+ .copied()
+ .rev()
+ .map(|p| (p, available_interrupt.pop().expect("UNREACHABLE")))
+ .collect();
+
+ Analysis {
parent: analysis,
- interrupts,
- })
+ interrupts_normal,
+ interrupts_async,
+ }
}
diff --git a/macros/src/bindings.rs b/macros/src/bindings.rs
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/macros/src/bindings.rs
diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs
index 89173d45..ef817325 100644
--- a/macros/src/codegen.rs
+++ b/macros/src/codegen.rs
@@ -1,10 +1,11 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
-use rtic_syntax::ast::App;
-use crate::{analyze::Analysis, check::Extra};
+use crate::analyze::Analysis;
+use crate::syntax::ast::App;
mod assertions;
+mod async_dispatchers;
mod dispatchers;
mod hardware_tasks;
mod idle;
@@ -12,6 +13,7 @@ mod init;
mod local_resources;
mod local_resources_struct;
mod module;
+mod monotonic;
mod post_init;
mod pre_init;
mod shared_resources;
@@ -21,22 +23,22 @@ mod timer_queue;
mod util;
#[allow(clippy::too_many_lines)]
-pub fn app(app: &App, analysis: &Analysis, extra: &Extra) -> TokenStream2 {
+pub fn app(app: &App, analysis: &Analysis) -> TokenStream2 {
let mut mod_app = vec![];
let mut mains = vec![];
let mut root = vec![];
let mut user = vec![];
// Generate the `main` function
- let assertion_stmts = assertions::codegen(app, analysis, extra);
+ let assertion_stmts = assertions::codegen(app, analysis);
- let pre_init_stmts = pre_init::codegen(app, analysis, extra);
+ let pre_init_stmts = pre_init::codegen(app, analysis);
- let (mod_app_init, root_init, user_init, call_init) = init::codegen(app, analysis, extra);
+ let (mod_app_init, root_init, user_init, call_init) = init::codegen(app, analysis);
let post_init_stmts = post_init::codegen(app, analysis);
- let (mod_app_idle, root_idle, user_idle, call_idle) = idle::codegen(app, analysis, extra);
+ let (mod_app_idle, root_idle, user_idle, call_idle) = idle::codegen(app, analysis);
user.push(quote!(
#user_init
@@ -84,82 +86,25 @@ pub fn app(app: &App, analysis: &Analysis, extra: &Extra) -> TokenStream2 {
}
));
- let (mod_app_shared_resources, mod_shared_resources) =
- shared_resources::codegen(app, analysis, extra);
- let (mod_app_local_resources, mod_local_resources) =
- local_resources::codegen(app, analysis, extra);
+ let (mod_app_shared_resources, mod_shared_resources) = shared_resources::codegen(app, analysis);
+ let (mod_app_local_resources, mod_local_resources) = local_resources::codegen(app, analysis);
let (mod_app_hardware_tasks, root_hardware_tasks, user_hardware_tasks) =
- hardware_tasks::codegen(app, analysis, extra);
+ hardware_tasks::codegen(app, analysis);
let (mod_app_software_tasks, root_software_tasks, user_software_tasks) =
- software_tasks::codegen(app, analysis, extra);
+ software_tasks::codegen(app, analysis);
- let mod_app_dispatchers = dispatchers::codegen(app, analysis, extra);
- let mod_app_timer_queue = timer_queue::codegen(app, analysis, extra);
+ let monotonics = monotonic::codegen(app, analysis);
+
+ let mod_app_dispatchers = dispatchers::codegen(app, analysis);
+ let mod_app_async_dispatchers = async_dispatchers::codegen(app, analysis);
+ let mod_app_timer_queue = timer_queue::codegen(app, analysis);
let user_imports = &app.user_imports;
let user_code = &app.user_code;
let name = &app.name;
- let device = &extra.device;
-
- let monotonic_parts: Vec<_> = app
- .monotonics
- .iter()
- .map(|(_, monotonic)| {
- let name = &monotonic.ident;
- let name_str = &name.to_string();
- let cfgs = &monotonic.cfgs;
- let ident = util::monotonic_ident(name_str);
- let doc = &format!(
- "This module holds the static implementation for `{}::now()`",
- name_str
- );
-
- let default_monotonic = if monotonic.args.default {
- quote!(
- #(#cfgs)*
- pub use #name::now;
- )
- } else {
- quote!()
- };
-
- quote! {
- #default_monotonic
-
- #[doc = #doc]
- #[allow(non_snake_case)]
- #(#cfgs)*
- pub mod #name {
-
- /// Read the current time from this monotonic
- pub fn now() -> <super::super::#name as rtic::Monotonic>::Instant {
- rtic::export::interrupt::free(|_| {
- use rtic::Monotonic as _;
- if let Some(m) = unsafe{ &mut *super::super::#ident.get_mut() } {
- m.now()
- } else {
- <super::super::#name as rtic::Monotonic>::zero()
- }
- })
- }
- }
- }
- })
- .collect();
-
- let monotonics = if monotonic_parts.is_empty() {
- quote!()
- } else {
- quote!(
- pub use rtic::Monotonic as _;
-
- /// Holds static methods for each monotonic.
- pub mod monotonics {
- #(#monotonic_parts)*
- }
- )
- };
+ let device = &app.args.device;
+
let rt_err = util::rt_err_ident();
quote!(
@@ -205,6 +150,8 @@ pub fn app(app: &App, analysis: &Analysis, extra: &Extra) -> TokenStream2 {
#(#mod_app_dispatchers)*
+ #(#mod_app_async_dispatchers)*
+
#(#mod_app_timer_queue)*
#(#mains)*
diff --git a/macros/src/codegen/assertions.rs b/macros/src/codegen/assertions.rs
index 3e0ad61c..0f8326c7 100644
--- a/macros/src/codegen/assertions.rs
+++ b/macros/src/codegen/assertions.rs
@@ -1,11 +1,11 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
-use crate::{analyze::Analysis, check::Extra, codegen::util};
-use rtic_syntax::ast::App;
+use crate::syntax::ast::App;
+use crate::{analyze::Analysis, codegen::util};
/// Generates compile-time assertions that check that types implement the `Send` / `Sync` traits
-pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec<TokenStream2> {
+pub fn codegen(app: &App, analysis: &Analysis) -> Vec<TokenStream2> {
let mut stmts = vec![];
for ty in &analysis.send_types {
@@ -21,7 +21,7 @@ pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec<TokenStream
stmts.push(quote!(rtic::export::assert_monotonic::<#ty>();));
}
- let device = &extra.device;
+ let device = &app.args.device;
let chunks_name = util::priority_mask_chunks_ident();
let no_basepri_checks: Vec<_> = app
.hardware_tasks
@@ -29,9 +29,7 @@ pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec<TokenStream
.filter_map(|(_, task)| {
if !util::is_exception(&task.args.binds) {
let interrupt_name = &task.args.binds;
- let cfgs = &task.cfgs;
Some(quote!(
- #(#cfgs)*
if (#device::Interrupt::#interrupt_name as usize) >= (#chunks_name * 32) {
::core::panic!("An interrupt out of range is used while in armv6 or armv8m.base");
}
diff --git a/macros/src/codegen/async_dispatchers.rs b/macros/src/codegen/async_dispatchers.rs
new file mode 100644
index 00000000..8b0e928b
--- /dev/null
+++ b/macros/src/codegen/async_dispatchers.rs
@@ -0,0 +1,129 @@
+use crate::syntax::ast::App;
+use crate::{analyze::Analysis, codegen::util};
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+
+/// Generates task dispatchers
+pub fn codegen(app: &App, analysis: &Analysis) -> Vec<TokenStream2> {
+ let mut items = vec![];
+
+ let interrupts = &analysis.interrupts_async;
+
+ // Generate executor definition and priority in global scope
+ for (name, task) in app.software_tasks.iter() {
+ if task.is_async {
+ let type_name = util::internal_task_ident(name, "F");
+ let exec_name = util::internal_task_ident(name, "EXEC");
+ let prio_name = util::internal_task_ident(name, "PRIORITY");
+
+ items.push(quote!(
+ #[allow(non_camel_case_types)]
+ type #type_name = impl core::future::Future + 'static;
+ #[allow(non_upper_case_globals)]
+ static #exec_name:
+ rtic::RacyCell<rtic::export::executor::AsyncTaskExecutor<#type_name>> =
+ rtic::RacyCell::new(rtic::export::executor::AsyncTaskExecutor::new());
+
+ // The executors priority, this can be any value - we will overwrite it when we
+ // start a task
+ #[allow(non_upper_case_globals)]
+ static #prio_name: rtic::RacyCell<rtic::export::Priority> =
+ unsafe { rtic::RacyCell::new(rtic::export::Priority::new(0)) };
+ ));
+ }
+ }
+
+ for (&level, channel) in &analysis.channels {
+ if channel
+ .tasks
+ .iter()
+ .map(|task_name| !app.software_tasks[task_name].is_async)
+ .all(|is_not_async| is_not_async)
+ {
+ // check if all tasks are not async, if so don't generate this.
+ continue;
+ }
+
+ let mut stmts = vec![];
+ let device = &app.args.device;
+ let enum_ = util::interrupt_ident();
+ let interrupt = util::suffixed(&interrupts[&level].0.to_string());
+
+ for name in channel
+ .tasks
+ .iter()
+ .filter(|name| app.software_tasks[*name].is_async)
+ {
+ let exec_name = util::internal_task_ident(name, "EXEC");
+ let prio_name = util::internal_task_ident(name, "PRIORITY");
+ let task = &app.software_tasks[name];
+ // let cfgs = &task.cfgs;
+ let (_, tupled, pats, input_types) = util::regroup_inputs(&task.inputs);
+ let executor_run_ident = util::executor_run_ident(name);
+
+ let n = util::capacity_literal(channel.capacity as usize + 1);
+ let rq = util::rq_async_ident(name);
+ let (rq_ty, rq_expr) = {
+ (
+ quote!(rtic::export::ASYNCRQ<#input_types, #n>),
+ quote!(rtic::export::Queue::new()),
+ )
+ };
+
+ items.push(quote!(
+ #[doc(hidden)]
+ #[allow(non_camel_case_types)]
+ #[allow(non_upper_case_globals)]
+ static #rq: rtic::RacyCell<#rq_ty> = rtic::RacyCell::new(#rq_expr);
+ ));
+
+ stmts.push(quote!(
+ if !(&*#exec_name.get()).is_running() {
+ if let Some(#tupled) = rtic::export::interrupt::free(|_| (&mut *#rq.get_mut()).dequeue()) {
+
+ // The async executor needs a static priority
+ #prio_name.get_mut().write(rtic::export::Priority::new(PRIORITY));
+ let priority: &'static _ = &*#prio_name.get();
+
+ (&mut *#exec_name.get_mut()).spawn(#name(#name::Context::new(priority) #(,#pats)*));
+ #executor_run_ident.store(true, core::sync::atomic::Ordering::Relaxed);
+ }
+ }
+
+ if #executor_run_ident.load(core::sync::atomic::Ordering::Relaxed) {
+ #executor_run_ident.store(false, core::sync::atomic::Ordering::Relaxed);
+ if (&mut *#exec_name.get_mut()).poll(|| {
+ #executor_run_ident.store(true, core::sync::atomic::Ordering::Release);
+ rtic::pend(#device::#enum_::#interrupt);
+ }) && !rtic::export::interrupt::free(|_| (&*#rq.get_mut()).is_empty()) {
+ // If the ready queue is not empty and the executor finished, restart this
+ // dispatch to check if the executor should be restarted.
+ rtic::pend(#device::#enum_::#interrupt);
+ }
+ }
+ ));
+ }
+
+ let doc = format!(
+ "Interrupt handler to dispatch async tasks at priority {}",
+ level
+ );
+ let attribute = &interrupts[&level].1.attrs;
+ items.push(quote!(
+ #[allow(non_snake_case)]
+ #[doc = #doc]
+ #[no_mangle]
+ #(#attribute)*
+ 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/dispatchers.rs b/macros/src/codegen/dispatchers.rs
index a90a97c7..1a8b4042 100644
--- a/macros/src/codegen/dispatchers.rs
+++ b/macros/src/codegen/dispatchers.rs
@@ -1,21 +1,31 @@
+use crate::syntax::ast::App;
+use crate::{analyze::Analysis, codegen::util};
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> {
+pub fn codegen(app: &App, analysis: &Analysis) -> Vec<TokenStream2> {
let mut items = vec![];
- let interrupts = &analysis.interrupts;
+ let interrupts = &analysis.interrupts_normal;
for (&level, channel) in &analysis.channels {
+ if channel
+ .tasks
+ .iter()
+ .map(|task_name| app.software_tasks[task_name].is_async)
+ .all(|is_async| is_async)
+ {
+ // check if all tasks are async, if so don't generate this.
+ continue;
+ }
+
let mut stmts = vec![];
let variants = channel
.tasks
.iter()
+ .filter(|name| !app.software_tasks[*name].is_async)
.map(|name| {
let cfgs = &app.software_tasks[name].cfgs;
@@ -45,6 +55,7 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec<TokenStrea
let n = util::capacity_literal(channel.capacity as usize + 1);
let rq = util::rq_ident(level);
+ // let (_, _, _, input_ty) = util::regroup_inputs(inputs);
let (rq_ty, rq_expr) = {
(
quote!(rtic::export::SCRQ<#t, #n>),
@@ -64,6 +75,13 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec<TokenStrea
static #rq: rtic::RacyCell<#rq_ty> = rtic::RacyCell::new(#rq_expr);
));
+ let interrupt = util::suffixed(
+ &interrupts
+ .get(&level)
+ .expect("RTIC-ICE: Unable to get interrrupt")
+ .0
+ .to_string(),
+ );
let arms = channel
.tasks
.iter()
@@ -74,23 +92,27 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec<TokenStrea
let inputs = util::inputs_ident(name);
let (_, tupled, pats, _) = util::regroup_inputs(&task.inputs);
- quote!(
- #(#cfgs)*
- #t::#name => {
- let #tupled =
- (&*#inputs
- .get())
- .get_unchecked(usize::from(index))
- .as_ptr()
- .read();
- (&mut *#fq.get_mut()).split().0.enqueue_unchecked(index);
- let priority = &rtic::export::Priority::new(PRIORITY);
- #name(
- #name::Context::new(priority)
- #(,#pats)*
- )
- }
- )
+ if !task.is_async {
+ quote!(
+ #(#cfgs)*
+ #t::#name => {
+ let #tupled =
+ (&*#inputs
+ .get())
+ .get_unchecked(usize::from(index))
+ .as_ptr()
+ .read();
+ (&mut *#fq.get_mut()).split().0.enqueue_unchecked(index);
+ let priority = &rtic::export::Priority::new(PRIORITY);
+ #name(
+ #name::Context::new(priority)
+ #(,#pats)*
+ )
+ }
+ )
+ } else {
+ quote!()
+ }
})
.collect::<Vec<_>>();
@@ -103,7 +125,6 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec<TokenStrea
));
let doc = format!("Interrupt handler to dispatch tasks at priority {}", level);
- let interrupt = util::suffixed(&interrupts[&level].0.to_string());
let attribute = &interrupts[&level].1.attrs;
items.push(quote!(
#[allow(non_snake_case)]
diff --git a/macros/src/codegen/hardware_tasks.rs b/macros/src/codegen/hardware_tasks.rs
index b3f05d2a..2a81d9a0 100644
--- a/macros/src/codegen/hardware_tasks.rs
+++ b/macros/src/codegen/hardware_tasks.rs
@@ -1,18 +1,15 @@
-use proc_macro2::TokenStream as TokenStream2;
-use quote::quote;
-use rtic_syntax::{ast::App, Context};
-
+use crate::syntax::{ast::App, Context};
use crate::{
analyze::Analysis,
- check::Extra,
codegen::{local_resources_struct, module, shared_resources_struct},
};
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
/// 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>,
@@ -33,12 +30,10 @@ pub fn codegen(
let priority = task.args.priority;
let cfgs = &task.cfgs;
let attrs = &task.attrs;
- let user_hardware_task_isr_doc = &format!(" User HW task ISR trampoline for {name}");
mod_app.push(quote!(
#[allow(non_snake_case)]
#[no_mangle]
- #[doc = #user_hardware_task_isr_doc]
#(#attrs)*
#(#cfgs)*
unsafe fn #symbol() {
@@ -87,19 +82,14 @@ pub fn codegen(
local_needs_lt,
app,
analysis,
- extra,
));
- let user_hardware_task_doc = &format!(" User HW task: {name}");
if !task.is_extern {
let attrs = &task.attrs;
- let cfgs = &task.cfgs;
let context = &task.context;
let stmts = &task.stmts;
user_tasks.push(quote!(
- #[doc = #user_hardware_task_doc]
#(#attrs)*
- #(#cfgs)*
#[allow(non_snake_case)]
fn #name(#context: #name::Context) {
use rtic::Mutex as _;
diff --git a/macros/src/codegen/idle.rs b/macros/src/codegen/idle.rs
index 77a7f9fe..98679399 100644
--- a/macros/src/codegen/idle.rs
+++ b/macros/src/codegen/idle.rs
@@ -1,18 +1,15 @@
-use proc_macro2::TokenStream as TokenStream2;
-use quote::quote;
-use rtic_syntax::{ast::App, Context};
-
+use crate::syntax::{ast::App, Context};
use crate::{
analyze::Analysis,
- check::Extra,
codegen::{local_resources_struct, module, shared_resources_struct},
};
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
/// Generates support code for `#[idle]` functions
pub fn codegen(
app: &App,
analysis: &Analysis,
- extra: &Extra,
) -> (
// mod_app_idle -- the `${idle}Resources` constructor
Vec<TokenStream2>,
@@ -57,16 +54,13 @@ pub fn codegen(
local_needs_lt,
app,
analysis,
- extra,
));
- let idle_doc = " User provided idle function".to_string();
let attrs = &idle.attrs;
let context = &idle.context;
let stmts = &idle.stmts;
let user_idle = Some(quote!(
#(#attrs)*
- #[doc = #idle_doc]
#[allow(non_snake_case)]
fn #name(#context: #name::Context) -> ! {
use rtic::Mutex as _;
@@ -82,6 +76,9 @@ pub fn codegen(
(mod_app, root_idle, user_idle, call_idle)
} else {
+ // TODO: No idle defined, check for 0-priority tasks and generate an executor if needed
+
+ //
(
vec![],
vec![],
diff --git a/macros/src/codegen/init.rs b/macros/src/codegen/init.rs
index 34f86f27..9a6fe2d5 100644
--- a/macros/src/codegen/init.rs
+++ b/macros/src/codegen/init.rs
@@ -1,11 +1,10 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
-use rtic_syntax::{ast::App, Context};
use crate::{
analyze::Analysis,
- check::Extra,
codegen::{local_resources_struct, module},
+ syntax::{ast::App, Context},
};
type CodegenResult = (
@@ -24,7 +23,7 @@ type CodegenResult = (
);
/// Generates support code for `#[init]` functions
-pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> CodegenResult {
+pub fn codegen(app: &App, analysis: &Analysis) -> CodegenResult {
let init = &app.init;
let mut local_needs_lt = false;
let name = &init.name;
@@ -65,27 +64,22 @@ pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> CodegenResult {
)
})
.collect();
-
- let shared_resources_doc = " RTIC shared resource struct".to_string();
- let local_resources_doc = " RTIC local resource struct".to_string();
root_init.push(quote! {
- #[doc = #shared_resources_doc]
struct #shared {
#(#shared_resources)*
}
- #[doc = #local_resources_doc]
struct #local {
#(#local_resources)*
}
});
+ // let locals_pat = locals_pat.iter();
+
let user_init_return = quote! {#shared, #local, #name::Monotonics};
- let user_init_doc = " User provided init function".to_string();
let user_init = quote!(
#(#attrs)*
- #[doc = #user_init_doc]
#[inline(always)]
#[allow(non_snake_case)]
fn #name(#context: #name::Context) -> (#user_init_return) {
@@ -105,6 +99,7 @@ pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> CodegenResult {
mod_app = Some(constructor);
}
+ // let locals_new = locals_new.iter();
let call_init = quote! {
let (shared_resources, local_resources, mut monotonics) = #name(#name::Context::new(core.into()));
};
@@ -115,7 +110,6 @@ pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> CodegenResult {
local_needs_lt,
app,
analysis,
- extra,
));
(mod_app, root_init, user_init, call_init)
diff --git a/macros/src/codegen/local_resources.rs b/macros/src/codegen/local_resources.rs
index 6e7c1daa..6fc63cd9 100644
--- a/macros/src/codegen/local_resources.rs
+++ b/macros/src/codegen/local_resources.rs
@@ -1,8 +1,7 @@
+use crate::syntax::ast::App;
+use crate::{analyze::Analysis, codegen::util};
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
-use rtic_syntax::ast::App;
-
-use crate::{analyze::Analysis, check::Extra, codegen::util};
/// Generates `local` variables and local resource proxies
///
@@ -10,7 +9,6 @@ use crate::{analyze::Analysis, check::Extra, codegen::util};
pub fn codegen(
app: &App,
_analysis: &Analysis,
- _extra: &Extra,
) -> (
// mod_app -- the `static` variables behind the proxies
Vec<TokenStream2>,
diff --git a/macros/src/codegen/local_resources_struct.rs b/macros/src/codegen/local_resources_struct.rs
index 74bdbf8b..309fd8d2 100644
--- a/macros/src/codegen/local_resources_struct.rs
+++ b/macros/src/codegen/local_resources_struct.rs
@@ -1,9 +1,9 @@
-use proc_macro2::TokenStream as TokenStream2;
-use quote::quote;
-use rtic_syntax::{
+use crate::syntax::{
ast::{App, TaskLocal},
Context,
};
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
use crate::codegen::util;
@@ -13,7 +13,13 @@ pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2,
let resources = match ctxt {
Context::Init => &app.init.args.local_resources,
- Context::Idle => &app.idle.as_ref().unwrap().args.local_resources,
+ Context::Idle => {
+ &app.idle
+ .as_ref()
+ .expect("RTIC-ICE: unable to get idle name")
+ .args
+ .local_resources
+ }
Context::HardwareTask(name) => &app.hardware_tasks[name].args.local_resources,
Context::SoftwareTask(name) => &app.software_tasks[name].args.local_resources,
};
@@ -49,9 +55,7 @@ pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2,
util::declared_static_local_resource_ident(name, &task_name)
};
- let local_resource_doc = format!(" Local resource `{name}`");
fields.push(quote!(
- #[doc = #local_resource_doc]
#(#cfgs)*
pub #name: &#lt mut #ty
));
@@ -84,7 +88,7 @@ pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2,
}
}
- let doc = format!(" Local resources `{}` has access to", ctxt.ident(app));
+ let doc = format!("Local resources `{}` has access to", ctxt.ident(app));
let ident = util::local_resources_ident(ctxt, app);
let item = quote!(
#[allow(non_snake_case)]
@@ -98,7 +102,6 @@ pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2,
let constructor = quote!(
impl<#lt> #ident<#lt> {
#[inline(always)]
- #[doc(hidden)]
pub unsafe fn new() -> Self {
#ident {
#(#values,)*
diff --git a/macros/src/codegen/module.rs b/macros/src/codegen/module.rs
index 8dcdbcf3..7ac06c5c 100644
--- a/macros/src/codegen/module.rs
+++ b/macros/src/codegen/module.rs
@@ -1,7 +1,7 @@
-use crate::{analyze::Analysis, check::Extra, codegen::util};
+use crate::syntax::{ast::App, Context};
+use crate::{analyze::Analysis, codegen::util};
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
-use rtic_syntax::{ast::App, Context};
#[allow(clippy::too_many_lines)]
pub fn codegen(
@@ -10,12 +10,13 @@ pub fn codegen(
local_resources_tick: bool,
app: &App,
analysis: &Analysis,
- extra: &Extra,
) -> TokenStream2 {
let mut items = vec![];
let mut module_items = vec![];
let mut fields = vec![];
let mut values = vec![];
+ // Used to copy task cfgs to the whole module
+ let mut task_cfgs = vec![];
let name = ctxt.ident(app);
@@ -27,8 +28,8 @@ pub fn codegen(
pub core: rtic::export::Peripherals
));
- if extra.peripherals {
- let device = &extra.device;
+ if app.args.peripherals {
+ let device = &app.args.device;
fields.push(quote!(
/// Device peripherals
@@ -52,14 +53,6 @@ pub fn codegen(
Context::Idle | Context::HardwareTask(_) | Context::SoftwareTask(_) => {}
}
- // if ctxt.has_locals(app) {
- // let ident = util::locals_ident(ctxt, app);
- // module_items.push(quote!(
- // #[doc(inline)]
- // pub use super::#ident as Locals;
- // ));
- // }
-
if ctxt.has_local_resources(app) {
let ident = util::local_resources_ident(ctxt, app);
let lt = if local_resources_tick {
@@ -114,12 +107,8 @@ pub fn codegen(
.monotonics
.iter()
.map(|(_, monotonic)| {
- let cfgs = &monotonic.cfgs;
let mono = &monotonic.ty;
- quote! {
- #(#cfgs)*
- pub #mono
- }
+ quote! {#mono}
})
.collect();
@@ -130,7 +119,7 @@ pub fn codegen(
#[allow(non_snake_case)]
#[allow(non_camel_case_types)]
pub struct #internal_monotonics_ident(
- #(#monotonic_types),*
+ #(pub #monotonic_types),*
);
));
@@ -141,10 +130,10 @@ pub fn codegen(
}
let doc = match ctxt {
- Context::Idle => " Idle loop",
- Context::Init => " Initialization function",
- Context::HardwareTask(_) => " Hardware task",
- Context::SoftwareTask(_) => " Software task",
+ Context::Idle => "Idle loop",
+ Context::Init => "Initialization function",
+ Context::HardwareTask(_) => "Hardware task",
+ Context::SoftwareTask(_) => "Software task",
};
let v = Vec::new();
@@ -175,8 +164,8 @@ pub fn codegen(
let internal_context_name = util::internal_task_ident(name, "Context");
items.push(quote!(
- /// Execution context
#(#cfgs)*
+ /// Execution context
#[allow(non_snake_case)]
#[allow(non_camel_case_types)]
pub struct #internal_context_name<#lt> {
@@ -185,7 +174,6 @@ pub fn codegen(
#(#cfgs)*
impl<#lt> #internal_context_name<#lt> {
- #[doc(hidden)]
#[inline(always)]
pub unsafe fn new(#core #priority) -> Self {
#internal_context_name {
@@ -196,8 +184,8 @@ pub fn codegen(
));
module_items.push(quote!(
- #[doc(inline)]
#(#cfgs)*
+ #[doc(inline)]
pub use super::#internal_context_name as Context;
));
@@ -206,6 +194,8 @@ pub fn codegen(
let priority = spawnee.args.priority;
let t = util::spawn_t_ident(priority);
let cfgs = &spawnee.cfgs;
+ // Store a copy of the task cfgs
+ task_cfgs = cfgs.clone();
let (args, tupled, untupled, ty) = util::regroup_inputs(&spawnee.inputs);
let args = &args;
let tupled = &tupled;
@@ -213,112 +203,141 @@ pub fn codegen(
let rq = util::rq_ident(priority);
let inputs = util::inputs_ident(name);
- let device = &extra.device;
+ let device = &app.args.device;
let enum_ = util::interrupt_ident();
- let interrupt = &analysis
- .interrupts
- .get(&priority)
- .expect("RTIC-ICE: interrupt identifer not found")
- .0;
+ let interrupt = if spawnee.is_async {
+ &analysis
+ .interrupts_async
+ .get(&priority)
+ .expect("RTIC-ICE: interrupt identifer not found")
+ .0
+ } else {
+ &analysis
+ .interrupts_normal
+ .get(&priority)
+ .expect("RTIC-ICE: interrupt identifer not found")
+ .0
+ };
let internal_spawn_ident = util::internal_task_ident(name, "spawn");
// Spawn caller
- items.push(quote!(
+ if spawnee.is_async {
+ let rq = util::rq_async_ident(name);
+ items.push(quote!(
- /// Spawns the task directly
- #(#cfgs)*
- pub fn #internal_spawn_ident(#(#args,)*) -> Result<(), #ty> {
- let input = #tupled;
+ #(#cfgs)*
+ /// Spawns the task directly
+ #[allow(non_snake_case)]
+ #[doc(hidden)]
+ pub fn #internal_spawn_ident(#(#args,)*) -> Result<(), #ty> {
+ let input = #tupled;
- unsafe {
- if let Some(index) = rtic::export::interrupt::free(|_| (&mut *#fq.get_mut()).dequeue()) {
- (&mut *#inputs
- .get_mut())
- .get_unchecked_mut(usize::from(index))
- .as_mut_ptr()
- .write(input);
+ unsafe {
+ let r = rtic::export::interrupt::free(|_| (&mut *#rq.get_mut()).enqueue(input));
- rtic::export::interrupt::free(|_| {
- (&mut *#rq.get_mut()).enqueue_unchecked((#t::#name, index));
- });
+ if r.is_ok() {
+ rtic::pend(#device::#enum_::#interrupt);
+ }
- rtic::pend(#device::#enum_::#interrupt);
+ r
+ }
+ }));
+ } else {
+ items.push(quote!(
- Ok(())
- } else {
- Err(input)
+ #(#cfgs)*
+ /// Spawns the task directly
+ #[allow(non_snake_case)]
+ #[doc(hidden)]
+ pub fn #internal_spawn_ident(#(#args,)*) -> Result<(), #ty> {
+ let input = #tupled;
+
+ unsafe {
+ if let Some(index) = rtic::export::interrupt::free(|_| (&mut *#fq.get_mut()).dequeue()) {
+ (&mut *#inputs
+ .get_mut())
+ .get_unchecked_mut(usize::from(index))
+ .as_mut_ptr()
+ .write(input);
+
+ rtic::export::interrupt::free(|_| {
+ (&mut *#rq.get_mut()).enqueue_unchecked((#t::#name, index));
+ });
+ rtic::pend(#device::#enum_::#interrupt);
+
+ Ok(())
+ } else {
+ Err(input)
+ }
}
- }
- }));
+ }));
+ }
module_items.push(quote!(
- #[doc(inline)]
#(#cfgs)*
+ #[doc(inline)]
pub use super::#internal_spawn_ident as spawn;
));
// Schedule caller
- for (_, monotonic) in &app.monotonics {
- let instants = util::monotonic_instants_ident(name, &monotonic.ident);
- let monotonic_name = monotonic.ident.to_string();
-
- let tq = util::tq_ident(&monotonic.ident.to_string());
- let t = util::schedule_t_ident();
- let m = &monotonic.ident;
- let cfgs = &monotonic.cfgs;
- let m_ident = util::monotonic_ident(&monotonic_name);
- let m_isr = &monotonic.args.binds;
- let enum_ = util::interrupt_ident();
- let spawn_handle_string = format!("{}::SpawnHandle", m);
-
- let (enable_interrupt, pend) = if &*m_isr.to_string() == "SysTick" {
- (
- quote!(core::mem::transmute::<_, rtic::export::SYST>(()).enable_interrupt()),
- quote!(rtic::export::SCB::set_pendst()),
- )
- } else {
- let rt_err = util::rt_err_ident();
- (
- quote!(rtic::export::NVIC::unmask(#rt_err::#enum_::#m_isr)),
- quote!(rtic::pend(#rt_err::#enum_::#m_isr)),
- )
- };
-
- let tq_marker = &util::timer_queue_marker_ident();
-
- // For future use
- // let doc = format!(" RTIC internal: {}:{}", file!(), line!());
- // items.push(quote!(#[doc = #doc]));
- let internal_spawn_handle_ident =
- util::internal_monotonics_ident(name, m, "SpawnHandle");
- let internal_spawn_at_ident = util::internal_monotonics_ident(name, m, "spawn_at");
- let internal_spawn_after_ident =
- util::internal_monotonics_ident(name, m, "spawn_after");
-
- if monotonic.args.default {
+ if !spawnee.is_async {
+ for (_, monotonic) in &app.monotonics {
+ let instants = util::monotonic_instants_ident(name, &monotonic.ident);
+ let monotonic_name = monotonic.ident.to_string();
+
+ let tq = util::tq_ident(&monotonic.ident.to_string());
+ let t = util::schedule_t_ident();
+ let m = &monotonic.ident;
+ let m_ident = util::monotonic_ident(&monotonic_name);
+ let m_isr = &monotonic.args.binds;
+ let enum_ = util::interrupt_ident();
+ let spawn_handle_string = format!("{}::SpawnHandle", m);
+
+ let (enable_interrupt, pend) = if &*m_isr.to_string() == "SysTick" {
+ (
+ quote!(core::mem::transmute::<_, rtic::export::SYST>(()).enable_interrupt()),
+ quote!(rtic::export::SCB::set_pendst()),
+ )
+ } else {
+ let rt_err = util::rt_err_ident();
+ (
+ quote!(rtic::export::NVIC::unmask(#rt_err::#enum_::#m_isr)),
+ quote!(rtic::pend(#rt_err::#enum_::#m_isr)),
+ )
+ };
+
+ let tq_marker = &util::timer_queue_marker_ident();
+
+ let internal_spawn_handle_ident =
+ util::internal_monotonics_ident(name, m, "SpawnHandle");
+ let internal_spawn_at_ident = util::internal_monotonics_ident(name, m, "spawn_at");
+ let internal_spawn_after_ident =
+ util::internal_monotonics_ident(name, m, "spawn_after");
+
+ if monotonic.args.default {
+ module_items.push(quote!(
+ #[doc(inline)]
+ pub use #m::spawn_after;
+ #[doc(inline)]
+ pub use #m::spawn_at;
+ #[doc(inline)]
+ pub use #m::SpawnHandle;
+ ));
+ }
module_items.push(quote!(
- #(#cfgs)*
- pub use #m::spawn_after;
- #(#cfgs)*
- pub use #m::spawn_at;
- #(#cfgs)*
- pub use #m::SpawnHandle;
+ pub mod #m {
+ #[doc(inline)]
+ pub use super::super::#internal_spawn_after_ident as spawn_after;
+ #[doc(inline)]
+ pub use super::super::#internal_spawn_at_ident as spawn_at;
+ #[doc(inline)]
+ pub use super::super::#internal_spawn_handle_ident as SpawnHandle;
+ }
));
- }
- module_items.push(quote!(
- #[doc(hidden)]
- #(#cfgs)*
- pub mod #m {
- pub use super::super::#internal_spawn_after_ident as spawn_after;
- pub use super::super::#internal_spawn_at_ident as spawn_at;
- pub use super::super::#internal_spawn_handle_ident as SpawnHandle;
- }
- ));
- items.push(quote!(
- #[doc(hidden)]
+ items.push(quote!(
#(#cfgs)*
#[allow(non_snake_case)]
#[allow(non_camel_case_types)]
@@ -329,7 +348,6 @@ pub fn codegen(
#(#cfgs)*
impl core::fmt::Debug for #internal_spawn_handle_ident {
- #[doc(hidden)]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct(#spawn_handle_string).finish()
}
@@ -340,7 +358,7 @@ pub fn codegen(
pub fn cancel(self) -> Result<#ty, ()> {
rtic::export::interrupt::free(|_| unsafe {
let tq = &mut *#tq.get_mut();
- if let Some((_task, index)) = tq.cancel_marker(self.marker) {
+ if let Some((_task, index)) = tq.cancel_task_marker(self.marker) {
// Get the message
let msg = (&*#inputs
.get())
@@ -357,9 +375,7 @@ pub fn codegen(
})
}
- /// Reschedule after
#[inline]
- #(#cfgs)*
pub fn reschedule_after(
self,
duration: <#m as rtic::Monotonic>::Duration
@@ -367,8 +383,6 @@ pub fn codegen(
self.reschedule_at(monotonics::#m::now() + duration)
}
- /// Reschedule at
- #(#cfgs)*
pub fn reschedule_at(
self,
instant: <#m as rtic::Monotonic>::Instant
@@ -379,16 +393,17 @@ pub fn codegen(
let tq = (&mut *#tq.get_mut());
- tq.update_marker(self.marker, marker, instant, || #pend).map(|_| #name::#m::SpawnHandle { marker })
+ tq.update_task_marker(self.marker, marker, instant, || #pend).map(|_| #name::#m::SpawnHandle { marker })
})
}
}
+
+ #(#cfgs)*
/// Spawns the task after a set duration relative to the current time
///
/// This will use the time `Instant::new(0)` as baseline if called in `#[init]`,
/// so if you use a non-resetable timer use `spawn_at` when in `#[init]`
- #(#cfgs)*
#[allow(non_snake_case)]
pub fn #internal_spawn_after_ident(
duration: <#m as rtic::Monotonic>::Duration
@@ -424,10 +439,10 @@ pub fn codegen(
rtic::export::interrupt::free(|_| {
let marker = #tq_marker.get().read();
- let nr = rtic::export::NotReady {
- instant,
- index,
+ let nr = rtic::export::TaskNotReady {
task: #t::#name,
+ index,
+ instant,
marker,
};
@@ -435,7 +450,7 @@ pub fn codegen(
let tq = &mut *#tq.get_mut();
- tq.enqueue_unchecked(
+ tq.enqueue_task_unchecked(
nr,
|| #enable_interrupt,
|| #pend,
@@ -449,6 +464,7 @@ pub fn codegen(
}
}
));
+ }
}
}
@@ -457,8 +473,9 @@ pub fn codegen(
} else {
quote!(
#(#items)*
+
#[allow(non_snake_case)]
- #(#cfgs)*
+ #(#task_cfgs)*
#[doc = #doc]
pub mod #name {
#(#module_items)*
diff --git a/macros/src/codegen/monotonic.rs b/macros/src/codegen/monotonic.rs
new file mode 100644
index 00000000..417a1d6a
--- /dev/null
+++ b/macros/src/codegen/monotonic.rs
@@ -0,0 +1,280 @@
+use crate::syntax::ast::App;
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+
+use crate::{analyze::Analysis, codegen::util};
+
+/// Generates monotonic module dispatchers
+pub fn codegen(app: &App, _analysis: &Analysis) -> TokenStream2 {
+ let mut monotonic_parts: Vec<_> = Vec::new();
+
+ let tq_marker = util::timer_queue_marker_ident();
+
+ for (_, monotonic) in &app.monotonics {
+ // let instants = util::monotonic_instants_ident(name, &monotonic.ident);
+ let monotonic_name = monotonic.ident.to_string();
+
+ let tq = util::tq_ident(&monotonic_name);
+ let m = &monotonic.ident;
+ let m_ident = util::monotonic_ident(&monotonic_name);
+ let m_isr = &monotonic.args.binds;
+ let enum_ = util::interrupt_ident();
+ let name_str = &m.to_string();
+ let ident = util::monotonic_ident(name_str);
+ let doc = &format!(
+ "This module holds the static implementation for `{}::now()`",
+ name_str
+ );
+
+ let (enable_interrupt, pend) = if &*m_isr.to_string() == "SysTick" {
+ (
+ quote!(core::mem::transmute::<_, rtic::export::SYST>(()).enable_interrupt()),
+ quote!(rtic::export::SCB::set_pendst()),
+ )
+ } else {
+ let rt_err = util::rt_err_ident();
+ (
+ quote!(rtic::export::NVIC::unmask(super::super::#rt_err::#enum_::#m_isr)),
+ quote!(rtic::pend(super::super::#rt_err::#enum_::#m_isr)),
+ )
+ };
+
+ let default_monotonic = if monotonic.args.default {
+ quote!(
+ #[doc(inline)]
+ pub use #m::now;
+ #[doc(inline)]
+ pub use #m::delay;
+ #[doc(inline)]
+ pub use #m::delay_until;
+ #[doc(inline)]
+ pub use #m::timeout_at;
+ #[doc(inline)]
+ pub use #m::timeout_after;
+ )
+ } else {
+ quote!()
+ };
+
+ monotonic_parts.push(quote! {
+ #default_monotonic
+
+ #[doc = #doc]
+ #[allow(non_snake_case)]
+ pub mod #m {
+ /// Read the current time from this monotonic
+ pub fn now() -> <super::super::#m as rtic::Monotonic>::Instant {
+ rtic::export::interrupt::free(|_| {
+ use rtic::Monotonic as _;
+ if let Some(m) = unsafe{ &mut *super::super::#ident.get_mut() } {
+ m.now()
+ } else {
+ <super::super::#m as rtic::Monotonic>::zero()
+ }
+ })
+ }
+
+ /// Delay
+ #[inline(always)]
+ #[allow(non_snake_case)]
+ pub fn delay(duration: <super::super::#m as rtic::Monotonic>::Duration)
+ -> DelayFuture {
+ let until = now() + duration;
+ DelayFuture { until, waker_storage: None }
+ }
+
+ /// Delay until a specific time
+ #[inline(always)]
+ #[allow(non_snake_case)]
+ pub fn delay_until(instant: <super::super::#m as rtic::Monotonic>::Instant)
+ -> DelayFuture {
+ let until = instant;
+ DelayFuture { until, waker_storage: None }
+ }
+
+ /// Delay future.
+ #[allow(non_snake_case)]
+ #[allow(non_camel_case_types)]
+ pub struct DelayFuture {
+ until: <super::super::#m as rtic::Monotonic>::Instant,
+ waker_storage: Option<rtic::export::IntrusiveNode<rtic::export::WakerNotReady<super::super::#m>>>,
+ }
+
+ impl Drop for DelayFuture {
+ fn drop(&mut self) {
+ if let Some(waker_storage) = &mut self.waker_storage {
+ rtic::export::interrupt::free(|_| unsafe {
+ let tq = &mut *super::super::#tq.get_mut();
+ tq.cancel_waker_marker(waker_storage.val.marker);
+ });
+ }
+ }
+ }
+
+ impl core::future::Future for DelayFuture {
+ type Output = ();
+
+ fn poll(
+ mut self: core::pin::Pin<&mut Self>,
+ cx: &mut core::task::Context<'_>
+ ) -> core::task::Poll<Self::Output> {
+ let mut s = self.as_mut();
+ let now = now();
+ let until = s.until;
+ let is_ws_none = s.waker_storage.is_none();
+
+ if now >= until {
+ return core::task::Poll::Ready(());
+ } else if is_ws_none {
+ rtic::export::interrupt::free(|_| unsafe {
+ let marker = super::super::#tq_marker.get().read();
+ super::super::#tq_marker.get_mut().write(marker.wrapping_add(1));
+
+ let nr = s.waker_storage.insert(rtic::export::IntrusiveNode::new(rtic::export::WakerNotReady {
+ waker: cx.waker().clone(),
+ instant: until,
+ marker,
+ }));
+
+ let tq = &mut *super::super::#tq.get_mut();
+
+ tq.enqueue_waker(
+ core::mem::transmute(nr), // Transmute the reference to static
+ || #enable_interrupt,
+ || #pend,
+ (&mut *super::super::#m_ident.get_mut()).as_mut());
+ });
+ }
+
+ core::task::Poll::Pending
+ }
+ }
+
+ /// Timeout future.
+ #[allow(non_snake_case)]
+ #[allow(non_camel_case_types)]
+ pub struct TimeoutFuture<F: core::future::Future> {
+ future: F,
+ until: <super::super::#m as rtic::Monotonic>::Instant,
+ waker_storage: Option<rtic::export::IntrusiveNode<rtic::export::WakerNotReady<super::super::#m>>>,
+ }
+
+ impl<F: core::future::Future> Drop for TimeoutFuture<F> {
+ fn drop(&mut self) {
+ if let Some(waker_storage) = &mut self.waker_storage {
+ rtic::export::interrupt::free(|_| unsafe {
+ let tq = &mut *super::super::#tq.get_mut();
+ tq.cancel_waker_marker(waker_storage.val.marker);
+ });
+ }
+ }
+ }
+
+ /// Timeout after
+ #[allow(non_snake_case)]
+ #[inline(always)]
+ pub fn timeout_after<F: core::future::Future>(
+ future: F,
+ duration: <super::super::#m as rtic::Monotonic>::Duration
+ ) -> TimeoutFuture<F> {
+ let until = now() + duration;
+ TimeoutFuture {
+ future,
+ until,
+ waker_storage: None,
+ }
+ }
+
+ /// Timeout at
+ #[allow(non_snake_case)]
+ #[inline(always)]
+ pub fn timeout_at<F: core::future::Future>(
+ future: F,
+ instant: <super::super::#m as rtic::Monotonic>::Instant
+ ) -> TimeoutFuture<F> {
+ TimeoutFuture {
+ future,
+ until: instant,
+ waker_storage: None,
+ }
+ }
+
+ impl<F> core::future::Future for TimeoutFuture<F>
+ where
+ F: core::future::Future,
+ {
+ type Output = Result<F::Output, super::TimeoutError>;
+
+ fn poll(
+ self: core::pin::Pin<&mut Self>,
+ cx: &mut core::task::Context<'_>
+ ) -> core::task::Poll<Self::Output> {
+ // SAFETY: We don't move the underlying pinned value.
+ let mut s = unsafe { self.get_unchecked_mut() };
+ let future = unsafe { core::pin::Pin::new_unchecked(&mut s.future) };
+ let now = now();
+ let until = s.until;
+ let is_ws_none = s.waker_storage.is_none();
+
+ match future.poll(cx) {
+ core::task::Poll::Ready(r) => {
+ if let Some(waker_storage) = &mut s.waker_storage {
+ rtic::export::interrupt::free(|_| unsafe {
+ let tq = &mut *super::super::#tq.get_mut();
+ tq.cancel_waker_marker(waker_storage.val.marker);
+ });
+ }
+
+ return core::task::Poll::Ready(Ok(r));
+ }
+ core::task::Poll::Pending => {
+ if now >= until {
+ // Timeout
+ return core::task::Poll::Ready(Err(super::TimeoutError));
+ } else if is_ws_none {
+ rtic::export::interrupt::free(|_| unsafe {
+ let marker = super::super::#tq_marker.get().read();
+ super::super::#tq_marker.get_mut().write(marker.wrapping_add(1));
+
+ let nr = s.waker_storage.insert(rtic::export::IntrusiveNode::new(rtic::export::WakerNotReady {
+ waker: cx.waker().clone(),
+ instant: until,
+ marker,
+ }));
+
+ let tq = &mut *super::super::#tq.get_mut();
+
+ tq.enqueue_waker(
+ core::mem::transmute(nr), // Transmute the reference to static
+ || #enable_interrupt,
+ || #pend,
+ (&mut *super::super::#m_ident.get_mut()).as_mut());
+ });
+ }
+ }
+ }
+
+ core::task::Poll::Pending
+ }
+ }
+ }
+ });
+ }
+
+ if monotonic_parts.is_empty() {
+ quote!()
+ } else {
+ quote!(
+ pub use rtic::Monotonic as _;
+
+ /// Holds static methods for each monotonic.
+ pub mod monotonics {
+ /// A timeout error.
+ #[derive(Debug)]
+ pub struct TimeoutError;
+
+ #(#monotonic_parts)*
+ }
+ )
+ }
+}
diff --git a/macros/src/codegen/post_init.rs b/macros/src/codegen/post_init.rs
index 460b4e21..df5daa1e 100644
--- a/macros/src/codegen/post_init.rs
+++ b/macros/src/codegen/post_init.rs
@@ -1,6 +1,6 @@
+use crate::syntax::ast::App;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::quote;
-use rtic_syntax::ast::App;
use syn::Index;
use crate::{analyze::Analysis, codegen::util};
@@ -43,28 +43,21 @@ pub fn codegen(app: &App, analysis: &Analysis) -> Vec<TokenStream2> {
}
}
- for (i, (monotonic_ident, monotonic)) in app.monotonics.iter().enumerate() {
+ for (i, (monotonic, _)) in app.monotonics.iter().enumerate() {
// For future use
// let doc = format!(" RTIC internal: {}:{}", file!(), line!());
// stmts.push(quote!(#[doc = #doc]));
- let cfgs = &monotonic.cfgs;
#[allow(clippy::cast_possible_truncation)]
let idx = Index {
index: i as u32,
span: Span::call_site(),
};
- stmts.push(quote!(
- #(#cfgs)*
- monotonics.#idx.reset();
- ));
+ stmts.push(quote!(monotonics.#idx.reset();));
// Store the monotonic
- let name = util::monotonic_ident(&monotonic_ident.to_string());
- stmts.push(quote!(
- #(#cfgs)*
- #name.get_mut().write(Some(monotonics.#idx));
- ));
+ let name = util::monotonic_ident(&monotonic.to_string());
+ stmts.push(quote!(#name.get_mut().write(Some(monotonics.#idx));));
}
// Enable the interrupts -- this completes the `init`-ialization phase
diff --git a/macros/src/codegen/pre_init.rs b/macros/src/codegen/pre_init.rs
index 2362cb74..ef3acba7 100644
--- a/macros/src/codegen/pre_init.rs
+++ b/macros/src/codegen/pre_init.rs
@@ -1,11 +1,11 @@
+use crate::syntax::ast::App;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
-use rtic_syntax::ast::App;
-use crate::{analyze::Analysis, check::Extra, codegen::util};
+use crate::{analyze::Analysis, codegen::util};
/// Generates code that runs before `#[init]`
-pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec<TokenStream2> {
+pub fn codegen(app: &App, analysis: &Analysis) -> Vec<TokenStream2> {
let mut stmts = vec![];
let rt_err = util::rt_err_ident();
@@ -15,12 +15,14 @@ pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec<TokenStream
// Populate the FreeQueue
for (name, task) in &app.software_tasks {
+ if task.is_async {
+ continue;
+ }
+
let cap = task.args.capacity;
- let cfgs = &task.cfgs;
let fq_ident = util::fq_ident(name);
stmts.push(quote!(
- #(#cfgs)*
(0..#cap).for_each(|i| (&mut *#fq_ident.get_mut()).enqueue_unchecked(i));
));
}
@@ -30,17 +32,21 @@ pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec<TokenStream
let mut core: rtic::export::Peripherals = rtic::export::Peripherals::steal().into();
));
- let device = &extra.device;
+ let device = &app.args.device;
let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS);
// check that all dispatchers exists in the `Interrupt` enumeration regardless of whether
// they are used or not
let interrupt = util::interrupt_ident();
- for name in app.args.extern_interrupts.keys() {
+ for name in app.args.dispatchers.keys() {
stmts.push(quote!(let _ = #rt_err::#interrupt::#name;));
}
- let interrupt_ids = analysis.interrupts.iter().map(|(p, (id, _))| (p, id));
+ let interrupt_ids = analysis
+ .interrupts_normal
+ .iter()
+ .map(|(p, (id, _))| (p, id))
+ .chain(analysis.interrupts_async.iter().map(|(p, (id, _))| (p, id)));
// Unmask interrupts and set their priorities
for (&priority, name) in interrupt_ids.chain(app.hardware_tasks.values().filter_map(|task| {
diff --git a/macros/src/codegen/shared_resources.rs b/macros/src/codegen/shared_resources.rs
index b5dff09d..66f38002 100644
--- a/macros/src/codegen/shared_resources.rs
+++ b/macros/src/codegen/shared_resources.rs
@@ -1,14 +1,13 @@
-use crate::{analyze::Analysis, check::Extra, codegen::util};
+use crate::syntax::{analyze::Ownership, ast::App};
+use crate::{analyze::Analysis, codegen::util};
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
-use rtic_syntax::{analyze::Ownership, ast::App};
use std::collections::HashMap;
/// Generates `static` variables and shared resource proxies
pub fn codegen(
app: &App,
analysis: &Analysis,
- extra: &Extra,
) -> (
// mod_app -- the `static` variables behind the proxies
Vec<TokenStream2>,
@@ -90,7 +89,7 @@ pub fn codegen(
// let doc = format!(" RTIC internal ({} resource): {}:{}", doc, file!(), line!());
mod_app.push(util::impl_mutex(
- extra,
+ app,
cfgs,
true,
&shared_name,
@@ -112,10 +111,14 @@ pub fn codegen(
};
// Computing mapping of used interrupts to masks
- let interrupt_ids = analysis.interrupts.iter().map(|(p, (id, _))| (p, id));
+ let interrupt_ids = analysis
+ .interrupts_normal
+ .iter()
+ .map(|(p, (id, _))| (p, id))
+ .chain(analysis.interrupts_async.iter().map(|(p, (id, _))| (p, id)));
let mut prio_to_masks = HashMap::new();
- let device = &extra.device;
+ let device = &app.args.device;
let mut uses_exceptions_with_resources = false;
let mut mask_ids = Vec::new();
@@ -147,8 +150,7 @@ pub fn codegen(
None
}
})) {
- #[allow(clippy::or_fun_call)]
- let v = prio_to_masks.entry(priority - 1).or_insert(Vec::new());
+ let v: &mut Vec<_> = prio_to_masks.entry(priority - 1).or_default();
v.push(quote!(#device::Interrupt::#name as u32));
mask_ids.push(quote!(#device::Interrupt::#name as u32));
}
diff --git a/macros/src/codegen/shared_resources_struct.rs b/macros/src/codegen/shared_resources_struct.rs
index df362719..1d46aa4e 100644
--- a/macros/src/codegen/shared_resources_struct.rs
+++ b/macros/src/codegen/shared_resources_struct.rs
@@ -1,6 +1,6 @@
+use crate::syntax::{ast::App, Context};
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
-use rtic_syntax::{ast::App, Context};
use crate::codegen::util;
@@ -10,24 +10,17 @@ pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2,
let resources = match ctxt {
Context::Init => unreachable!("Tried to generate shared resources struct for init"),
- Context::Idle => &app.idle.as_ref().unwrap().args.shared_resources,
+ Context::Idle => {
+ &app.idle
+ .as_ref()
+ .expect("RTIC-ICE: unable to get idle name")
+ .args
+ .shared_resources
+ }
Context::HardwareTask(name) => &app.hardware_tasks[name].args.shared_resources,
Context::SoftwareTask(name) => &app.software_tasks[name].args.shared_resources,
};
- let v = Vec::new();
- let task_cfgs = match ctxt {
- Context::HardwareTask(t) => {
- &app.hardware_tasks[t].cfgs
- // ...
- }
- Context::SoftwareTask(t) => {
- &app.software_tasks[t].cfgs
- // ...
- }
- _ => &v,
- };
-
let mut fields = vec![];
let mut values = vec![];
let mut has_cfgs = false;
@@ -57,18 +50,14 @@ pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2,
quote!('a)
};
- let lock_free_resource_doc = format!(" Lock free resource `{name}`");
fields.push(quote!(
- #[doc = #lock_free_resource_doc]
#(#cfgs)*
pub #name: &#lt #mut_ #ty
));
} else if access.is_shared() {
lt = Some(quote!('a));
- let shared_resource_doc = format!(" Shared resource `{name}`");
fields.push(quote!(
- #[doc = #shared_resource_doc]
#(#cfgs)*
pub #name: &'a #ty
));
@@ -76,16 +65,12 @@ pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2,
// Resource proxy
lt = Some(quote!('a));
- let resource_doc =
- format!(" Resource proxy resource `{name}`. Use method `.lock()` to gain access");
fields.push(quote!(
- #[doc = #resource_doc]
#(#cfgs)*
pub #name: shared_resources::#shared_name<'a>
));
values.push(quote!(
- #[doc(hidden)]
#(#cfgs)*
#name: shared_resources::#shared_name::new(priority)
@@ -95,17 +80,13 @@ pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2,
continue;
}
- let resource_doc;
let expr = if access.is_exclusive() {
- resource_doc = format!(" Exclusive access resource `{name}`");
quote!(&mut *(&mut *#mangled_name.get_mut()).as_mut_ptr())
} else {
- resource_doc = format!(" Non-exclusive access resource `{name}`");
quote!(&*(&*#mangled_name.get()).as_ptr())
};
values.push(quote!(
- #[doc = #resource_doc]
#(#cfgs)*
#name: #expr
));
@@ -125,13 +106,12 @@ pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2,
}
}
- let doc = format!(" Shared resources `{}` has access to", ctxt.ident(app));
+ let doc = format!("Shared resources `{}` has access to", ctxt.ident(app));
let ident = util::shared_resources_ident(ctxt, app);
let item = quote!(
#[allow(non_snake_case)]
#[allow(non_camel_case_types)]
#[doc = #doc]
- #(#task_cfgs)*
pub struct #ident<#lt> {
#(#fields,)*
}
@@ -143,9 +123,7 @@ pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2,
Some(quote!(priority: &#lt rtic::export::Priority))
};
let constructor = quote!(
- #(#task_cfgs)*
impl<#lt> #ident<#lt> {
- #[doc(hidden)]
#[inline(always)]
pub unsafe fn new(#arg) -> Self {
#ident {
diff --git a/macros/src/codegen/software_tasks.rs b/macros/src/codegen/software_tasks.rs
index 226121dd..f9247dae 100644
--- a/macros/src/codegen/software_tasks.rs
+++ b/macros/src/codegen/software_tasks.rs
@@ -1,17 +1,14 @@
-use proc_macro2::TokenStream as TokenStream2;
-use quote::quote;
-use rtic_syntax::{ast::App, Context};
-
+use crate::syntax::{ast::App, Context};
use crate::{
analyze::Analysis,
- check::Extra,
codegen::{local_resources_struct, module, shared_resources_struct, util},
};
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
pub fn codegen(
app: &App,
analysis: &Analysis,
- extra: &Extra,
) -> (
// mod_app_software_tasks -- free queues, buffers and `${task}Resources` constructors
Vec<TokenStream2>,
@@ -27,74 +24,87 @@ pub fn codegen(
let mut root = vec![];
let mut user_tasks = vec![];
- for (name, task) in &app.software_tasks {
+ // Any task
+ for (name, task) in app.software_tasks.iter() {
let inputs = &task.inputs;
- let cfgs = &task.cfgs;
let (_, _, _, input_ty) = util::regroup_inputs(inputs);
let cap = task.args.capacity;
let cap_lit = util::capacity_literal(cap as usize);
let cap_lit_p1 = util::capacity_literal(cap as usize + 1);
- // Create free queues and inputs / instants buffers
- let fq = util::fq_ident(name);
-
- #[allow(clippy::redundant_closure)]
- let (fq_ty, fq_expr, mk_uninit): (_, _, Box<dyn Fn() -> Option<_>>) = {
- (
- quote!(rtic::export::SCFQ<#cap_lit_p1>),
- quote!(rtic::export::Queue::new()),
- Box::new(|| Some(util::link_section_uninit())),
- )
- };
- mod_app.push(quote!(
- // /// Queue version of a free-list that keeps track of empty slots in
- // /// the following buffers
- #(#cfgs)*
- #[allow(non_camel_case_types)]
- #[allow(non_upper_case_globals)]
- #[doc(hidden)]
- static #fq: rtic::RacyCell<#fq_ty> = rtic::RacyCell::new(#fq_expr);
- ));
-
- let elems = &(0..cap)
- .map(|_| quote!(core::mem::MaybeUninit::uninit()))
- .collect::<Vec<_>>();
+ if !task.is_async {
+ // Create free queues and inputs / instants buffers
+ let fq = util::fq_ident(name);
- for (_, monotonic) in &app.monotonics {
- let instants = util::monotonic_instants_ident(name, &monotonic.ident);
- let mono_type = &monotonic.ty;
- let cfgs = &monotonic.cfgs;
+ #[allow(clippy::redundant_closure)]
+ let (fq_ty, fq_expr, mk_uninit): (_, _, Box<dyn Fn() -> Option<_>>) = {
+ (
+ quote!(rtic::export::SCFQ<#cap_lit_p1>),
+ quote!(rtic::export::Queue::new()),
+ Box::new(|| Some(util::link_section_uninit())),
+ )
+ };
- let uninit = mk_uninit();
- // For future use
- // let doc = format!(" RTIC internal: {}:{}", file!(), line!());
mod_app.push(quote!(
+ // /// Queue version of a free-list that keeps track of empty slots in
+ // /// the following buffers
+ #[allow(non_camel_case_types)]
+ #[allow(non_upper_case_globals)]
+ #[doc(hidden)]
+ static #fq: rtic::RacyCell<#fq_ty> = rtic::RacyCell::new(#fq_expr);
+ ));
+
+ let elems = &(0..cap)
+ .map(|_| quote!(core::mem::MaybeUninit::uninit()))
+ .collect::<Vec<_>>();
+
+ for (_, monotonic) in &app.monotonics {
+ let instants = util::monotonic_instants_ident(name, &monotonic.ident);
+ let mono_type = &monotonic.ty;
+
+ let uninit = mk_uninit();
+ // For future use
+ // let doc = format!(" RTIC internal: {}:{}", file!(), line!());
+ mod_app.push(quote!(
#uninit
// /// Buffer that holds the instants associated to the inputs of a task
// #[doc = #doc]
#[allow(non_camel_case_types)]
#[allow(non_upper_case_globals)]
#[doc(hidden)]
- #(#cfgs)*
static #instants:
rtic::RacyCell<[core::mem::MaybeUninit<<#mono_type as rtic::Monotonic>::Instant>; #cap_lit]> =
rtic::RacyCell::new([#(#elems,)*]);
));
+ }
+
+ let uninit = mk_uninit();
+ let inputs_ident = util::inputs_ident(name);
+
+ // Buffer that holds the inputs of a task
+ mod_app.push(quote!(
+ #uninit
+ #[allow(non_camel_case_types)]
+ #[allow(non_upper_case_globals)]
+ #[doc(hidden)]
+ static #inputs_ident: rtic::RacyCell<[core::mem::MaybeUninit<#input_ty>; #cap_lit]> =
+ rtic::RacyCell::new([#(#elems,)*]);
+ ));
}
- let uninit = mk_uninit();
- let inputs_ident = util::inputs_ident(name);
- mod_app.push(quote!(
- #uninit
- // /// Buffer that holds the inputs of a task
- #[allow(non_camel_case_types)]
- #[allow(non_upper_case_globals)]
- #[doc(hidden)]
- #(#cfgs)*
- static #inputs_ident: rtic::RacyCell<[core::mem::MaybeUninit<#input_ty>; #cap_lit]> =
- rtic::RacyCell::new([#(#elems,)*]);
- ));
+ if task.is_async {
+ let executor_ident = util::executor_run_ident(name);
+ mod_app.push(quote!(
+ #[allow(non_camel_case_types)]
+ #[allow(non_upper_case_globals)]
+ #[doc(hidden)]
+ static #executor_ident: core::sync::atomic::AtomicBool =
+ core::sync::atomic::AtomicBool::new(false);
+ ));
+ }
+
+ let inputs = &task.inputs;
// `${task}Resources`
let mut shared_needs_lt = false;
@@ -130,13 +140,24 @@ pub fn codegen(
let attrs = &task.attrs;
let cfgs = &task.cfgs;
let stmts = &task.stmts;
- let user_task_doc = format!(" User SW task {name}");
+ let (async_marker, context_lifetime) = if task.is_async {
+ (
+ quote!(async),
+ if shared_needs_lt || local_needs_lt {
+ quote!(<'static>)
+ } else {
+ quote!()
+ },
+ )
+ } else {
+ (quote!(), quote!())
+ };
+
user_tasks.push(quote!(
- #[doc = #user_task_doc]
#(#attrs)*
#(#cfgs)*
#[allow(non_snake_case)]
- fn #name(#context: #name::Context #(,#inputs)*) {
+ #async_marker fn #name(#context: #name::Context #context_lifetime #(,#inputs)*) {
use rtic::Mutex as _;
use rtic::mutex::prelude::*;
@@ -151,7 +172,6 @@ pub fn codegen(
local_needs_lt,
app,
analysis,
- extra,
));
}
diff --git a/macros/src/codegen/timer_queue.rs b/macros/src/codegen/timer_queue.rs
index f5867dc4..281148d9 100644
--- a/macros/src/codegen/timer_queue.rs
+++ b/macros/src/codegen/timer_queue.rs
@@ -1,18 +1,18 @@
+use crate::syntax::ast::App;
+use crate::{analyze::Analysis, codegen::util};
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
#[allow(clippy::too_many_lines)]
-pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec<TokenStream2> {
+pub fn codegen(app: &App, analysis: &Analysis) -> Vec<TokenStream2> {
let mut items = vec![];
if !app.monotonics.is_empty() {
// Generate the marker counter used to track for `cancel` and `reschedule`
let tq_marker = util::timer_queue_marker_ident();
items.push(quote!(
+ // #[doc = #doc]
#[doc(hidden)]
#[allow(non_camel_case_types)]
#[allow(non_upper_case_globals)]
@@ -26,6 +26,7 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec<TokenStrea
let variants = app
.software_tasks
.iter()
+ .filter(|(_, task)| !task.is_async)
.map(|(name, task)| {
let cfgs = &task.cfgs;
@@ -55,7 +56,6 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec<TokenStrea
let tq = util::tq_ident(&monotonic_name);
let t = util::schedule_t_ident();
let mono_type = &monotonic.ty;
- let cfgs = &monotonic.cfgs;
let m_ident = util::monotonic_ident(&monotonic_name);
// Static variables and resource proxy
@@ -67,8 +67,8 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec<TokenStrea
.iter()
.map(|(_name, task)| task.args.capacity as usize)
.sum();
- let n = util::capacity_literal(cap);
- let tq_ty = quote!(rtic::export::TimerQueue<#mono_type, #t, #n>);
+ let n_task = util::capacity_literal(cap);
+ let tq_ty = quote!(rtic::export::TimerQueue<#mono_type, #t, #n_task>);
// For future use
// let doc = format!(" RTIC internal: {}:{}", file!(), line!());
@@ -76,9 +76,12 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec<TokenStrea
#[doc(hidden)]
#[allow(non_camel_case_types)]
#[allow(non_upper_case_globals)]
- #(#cfgs)*
- static #tq: rtic::RacyCell<#tq_ty> =
- rtic::RacyCell::new(rtic::export::TimerQueue(rtic::export::SortedLinkedList::new_u16()));
+ static #tq: rtic::RacyCell<#tq_ty> = rtic::RacyCell::new(
+ rtic::export::TimerQueue {
+ task_queue: rtic::export::SortedLinkedList::new_u16(),
+ waker_queue: rtic::export::IntrusiveSortedLinkedList::new(),
+ }
+ );
));
let mono = util::monotonic_ident(&monotonic_name);
@@ -89,7 +92,6 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec<TokenStrea
#[doc(hidden)]
#[allow(non_camel_case_types)]
#[allow(non_upper_case_globals)]
- #(#cfgs)*
static #mono: rtic::RacyCell<Option<#mono_type>> = rtic::RacyCell::new(None);
));
}
@@ -102,6 +104,7 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec<TokenStrea
let arms = app
.software_tasks
.iter()
+ .filter(|(_, task)| !task.is_async)
.map(|(name, task)| {
let cfgs = &task.cfgs;
let priority = task.args.priority;
@@ -109,7 +112,7 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec<TokenStrea
let rqt = util::spawn_t_ident(priority);
// The interrupt that runs the task dispatcher
- let interrupt = &analysis.interrupts.get(&priority).expect("RTIC-ICE: interrupt not found").0;
+ let interrupt = &analysis.interrupts_normal.get(&priority).expect("RTIC-ICE: interrupt not found").0;
let pend = {
quote!(
@@ -120,7 +123,9 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec<TokenStrea
quote!(
#(#cfgs)*
#t::#name => {
- rtic::export::interrupt::free(|_| (&mut *#rq.get_mut()).split().0.enqueue_unchecked((#rqt::#name, index)));
+ rtic::export::interrupt::free(|_|
+ (&mut *#rq.get_mut()).split().0.enqueue_unchecked((#rqt::#name, index))
+ );
#pend
}
@@ -128,7 +133,6 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec<TokenStrea
})
.collect::<Vec<_>>();
- let cfgs = &monotonic.cfgs;
let bound_interrupt = &monotonic.args.binds;
let disable_isr = if &*bound_interrupt.to_string() == "SysTick" {
quote!(core::mem::transmute::<_, rtic::export::SYST>(()).disable_interrupt())
@@ -139,7 +143,6 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec<TokenStrea
items.push(quote!(
#[no_mangle]
#[allow(non_snake_case)]
- #(#cfgs)*
unsafe fn #bound_interrupt() {
while let Some((task, index)) = rtic::export::interrupt::free(|_|
if let Some(mono) = (&mut *#m_ident.get_mut()).as_mut() {
diff --git a/macros/src/codegen/util.rs b/macros/src/codegen/util.rs
index 0a3edc20..151906da 100644
--- a/macros/src/codegen/util.rs
+++ b/macros/src/codegen/util.rs
@@ -1,12 +1,10 @@
use core::sync::atomic::{AtomicUsize, Ordering};
+use crate::syntax::{ast::App, Context};
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;
-
const RTIC_INTERNAL: &str = "__rtic_internal";
/// Turns `capacity` into an unsuffixed integer literal
@@ -21,7 +19,7 @@ pub fn fq_ident(task: &Ident) -> Ident {
/// Generates a `Mutex` implementation
pub fn impl_mutex(
- extra: &Extra,
+ app: &App,
cfgs: &[Attribute],
resources_prefix: bool,
name: &Ident,
@@ -35,7 +33,7 @@ pub fn impl_mutex(
(quote!(#name), quote!(self.priority))
};
- let device = &extra.device;
+ let device = &app.args.device;
let masks_name = priority_masks_ident();
quote!(
#(#cfgs)*
@@ -67,6 +65,11 @@ pub fn inputs_ident(task: &Ident) -> Ident {
mark_internal_name(&format!("{}_INPUTS", task))
}
+/// Generates an identifier for the `EXECUTOR_RUN` atomics (`async` API)
+pub fn executor_run_ident(task: &Ident) -> Ident {
+ mark_internal_name(&format!("{}_EXECUTOR_RUN", task))
+}
+
/// Generates an identifier for the `INSTANTS` buffer (`schedule` API)
pub fn monotonic_instants_ident(task: &Ident, monotonic: &Ident) -> Ident {
mark_internal_name(&format!("{}_{}_INSTANTS", task, monotonic))
@@ -179,7 +182,12 @@ pub fn regroup_inputs(
pub fn get_task_name(ctxt: Context, app: &App) -> Ident {
let s = match ctxt {
Context::Init => app.init.name.to_string(),
- Context::Idle => app.idle.as_ref().unwrap().name.to_string(),
+ Context::Idle => app
+ .idle
+ .as_ref()
+ .expect("RTIC-ICE: unable to find idle name")
+ .name
+ .to_string(),
Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(),
};
@@ -190,7 +198,12 @@ pub fn get_task_name(ctxt: Context, app: &App) -> Ident {
pub fn shared_resources_ident(ctxt: Context, app: &App) -> Ident {
let mut s = match ctxt {
Context::Init => app.init.name.to_string(),
- Context::Idle => app.idle.as_ref().unwrap().name.to_string(),
+ Context::Idle => app
+ .idle
+ .as_ref()
+ .expect("RTIC-ICE: unable to find idle name")
+ .name
+ .to_string(),
Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(),
};
@@ -203,7 +216,12 @@ pub fn shared_resources_ident(ctxt: Context, app: &App) -> Ident {
pub fn local_resources_ident(ctxt: Context, app: &App) -> Ident {
let mut s = match ctxt {
Context::Init => app.init.name.to_string(),
- Context::Idle => app.idle.as_ref().unwrap().name.to_string(),
+ Context::Idle => app
+ .idle
+ .as_ref()
+ .expect("RTIC-ICE: unable to find idle name")
+ .name
+ .to_string(),
Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(),
};
@@ -220,9 +238,14 @@ pub fn rq_ident(priority: u8) -> Ident {
mark_internal_name(&format!("P{}_RQ", priority))
}
+/// Generates an identifier for a ready queue, async task version
+pub fn rq_async_ident(async_task_name: &Ident) -> Ident {
+ mark_internal_name(&format!("ASYNC_TACK_{}_RQ", async_task_name))
+}
+
/// Generates an identifier for the `enum` of `schedule`-able tasks
pub fn schedule_t_ident() -> Ident {
- Ident::new("SCHED_T", Span::call_site())
+ mark_internal_name("SCHED_T")
}
/// Generates an identifier for the `enum` of `spawn`-able tasks
@@ -230,7 +253,7 @@ pub fn schedule_t_ident() -> Ident {
/// 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())
+ mark_internal_name(&format!("P{}_T", priority))
}
/// Suffixed identifier
diff --git a/macros/src/lib.rs b/macros/src/lib.rs
index 2b526017..7729dcbe 100644
--- a/macros/src/lib.rs
+++ b/macros/src/lib.rs
@@ -1,21 +1,46 @@
#![doc(
- html_logo_url = "https://raw.githubusercontent.com/rtic-rs/cortex-m-rtic/master/book/en/src/RTIC.svg",
- html_favicon_url = "https://raw.githubusercontent.com/rtic-rs/cortex-m-rtic/master/book/en/src/RTIC.svg"
+ html_logo_url = "https://raw.githubusercontent.com/rtic-rs/rtic/master/book/en/src/RTIC.svg",
+ html_favicon_url = "https://raw.githubusercontent.com/rtic-rs/rtic/master/book/en/src/RTIC.svg"
)]
-//deny_warnings_placeholder_for_ci
-extern crate proc_macro;
+//deny_warnings_placeholder_for_ci
use proc_macro::TokenStream;
use std::{env, fs, path::Path};
-use rtic_syntax::Settings;
-
mod analyze;
-mod check;
+mod bindings;
mod codegen;
-#[cfg(test)]
-mod tests;
+mod syntax;
+
+// Used for mocking the API in testing
+#[doc(hidden)]
+#[proc_macro_attribute]
+pub fn mock_app(args: TokenStream, input: TokenStream) -> TokenStream {
+ let mut settings = syntax::Settings::default();
+ let mut rtic_args = vec![];
+ for arg in args.to_string().split(',') {
+ if arg.trim() == "parse_binds" {
+ settings.parse_binds = true;
+ } else if arg.trim() == "parse_extern_interrupt" {
+ settings.parse_extern_interrupt = true;
+ } else {
+ rtic_args.push(arg.to_string());
+ }
+ }
+
+ // rtic_args.push("device = mock".into());
+
+ let args = rtic_args.join(", ").parse();
+
+ println!("args: {:?}", args);
+
+ if let Err(e) = syntax::parse(args.unwrap(), input, settings) {
+ e.to_compile_error().into()
+ } else {
+ "fn main() {}".parse().unwrap()
+ }
+}
/// Attribute used to declare a RTIC application
///
@@ -26,24 +51,19 @@ mod tests;
/// Should never panic, cargo feeds a path which is later converted to a string
#[proc_macro_attribute]
pub fn app(args: TokenStream, input: TokenStream) -> TokenStream {
- let mut settings = Settings::default();
+ let mut settings = syntax::Settings::default();
settings.optimize_priorities = false;
settings.parse_binds = true;
settings.parse_extern_interrupt = true;
- let (app, analysis) = match rtic_syntax::parse(args, input, settings) {
- Err(e) => return e.to_compile_error().into(),
- Ok(x) => x,
- };
-
- let extra = match check::app(&app, &analysis) {
+ let (app, analysis) = match syntax::parse(args, input, settings) {
Err(e) => return e.to_compile_error().into(),
Ok(x) => x,
};
let analysis = analyze::app(analysis, &app);
- let ts = codegen::app(&app, &analysis, &extra);
+ let ts = codegen::app(&app, &analysis);
// Default output path: <project_dir>/target/
let mut out_dir = Path::new("target");
@@ -52,22 +72,7 @@ pub fn app(args: TokenStream, input: TokenStream) -> TokenStream {
// TODO don't want to break builds if OUT_DIR is not set, is this ever the case?
let out_str = env::var("OUT_DIR").unwrap_or_else(|_| "".to_string());
- // Assuming we are building for a thumbv* target
- let target_triple_prefix = "thumbv";
-
- // Check for special scenario where default target/ directory is not present
- //
- // This is configurable in .cargo/config:
- //
- // [build]
- // target-dir = "target"
- #[cfg(feature = "debugprint")]
- println!("OUT_DIR\n{:#?}", out_str);
-
- if out_dir.exists() {
- #[cfg(feature = "debugprint")]
- println!("\ntarget/ exists\n");
- } else {
+ if !out_dir.exists() {
// Set out_dir to OUT_DIR
out_dir = Path::new(&out_str);
@@ -81,16 +86,11 @@ pub fn app(args: TokenStream, input: TokenStream) -> TokenStream {
// If no "target" directory is found, <project_dir>/<out_dir_root> is used
for path in out_dir.ancestors() {
if let Some(dir) = path.components().last() {
- if dir
- .as_os_str()
- .to_str()
- .unwrap()
- .starts_with(target_triple_prefix)
- {
+ let dir = dir.as_os_str().to_str().unwrap();
+
+ if dir.starts_with("thumbv") || dir.starts_with("riscv") {
if let Some(out) = path.parent() {
out_dir = out;
- #[cfg(feature = "debugprint")]
- println!("{:#?}\n", out_dir);
break;
}
// If no parent, just use it
@@ -103,8 +103,6 @@ pub fn app(args: TokenStream, input: TokenStream) -> TokenStream {
// Try to write the expanded code to disk
if let Some(out_str) = out_dir.to_str() {
- #[cfg(feature = "debugprint")]
- println!("Write file:\n{}/rtic-expansion.rs\n", out_str);
fs::write(format!("{}/rtic-expansion.rs", out_str), ts.to_string()).ok();
}
diff --git a/macros/src/syntax.rs b/macros/src/syntax.rs
new file mode 100644
index 00000000..11b92c1b
--- /dev/null
+++ b/macros/src/syntax.rs
@@ -0,0 +1,158 @@
+#[allow(unused_extern_crates)]
+extern crate proc_macro;
+
+use core::ops;
+use proc_macro::TokenStream;
+
+use indexmap::{IndexMap, IndexSet};
+use proc_macro2::TokenStream as TokenStream2;
+use syn::Ident;
+
+use crate::syntax::ast::App;
+
+mod accessors;
+pub mod analyze;
+pub mod ast;
+mod check;
+mod optimize;
+mod parse;
+
+/// An ordered map keyed by identifier
+pub type Map<T> = IndexMap<Ident, T>;
+
+/// An order set
+pub type Set<T> = IndexSet<T>;
+
+/// Immutable pointer
+pub struct P<T> {
+ ptr: Box<T>,
+}
+
+impl<T> P<T> {
+ /// Boxes `x` making the value immutable
+ pub fn new(x: T) -> P<T> {
+ P { ptr: Box::new(x) }
+ }
+}
+
+impl<T> ops::Deref for P<T> {
+ type Target = T;
+
+ fn deref(&self) -> &T {
+ &self.ptr
+ }
+}
+
+/// Execution context
+#[derive(Clone, Copy)]
+pub enum Context<'a> {
+ /// The `idle` context
+ Idle,
+
+ /// The `init`-ialization function
+ Init,
+
+ /// A software task: `#[task]`
+ SoftwareTask(&'a Ident),
+
+ /// A hardware task: `#[exception]` or `#[interrupt]`
+ HardwareTask(&'a Ident),
+}
+
+impl<'a> Context<'a> {
+ /// The identifier of this context
+ pub fn ident(&self, app: &'a App) -> &'a Ident {
+ match self {
+ Context::HardwareTask(ident) => ident,
+ Context::Idle => &app.idle.as_ref().unwrap().name,
+ Context::Init => &app.init.name,
+ Context::SoftwareTask(ident) => ident,
+ }
+ }
+
+ /// Is this the `idle` context?
+ pub fn is_idle(&self) -> bool {
+ matches!(self, Context::Idle)
+ }
+
+ /// Is this the `init`-ialization context?
+ pub fn is_init(&self) -> bool {
+ matches!(self, Context::Init)
+ }
+
+ /// Whether this context runs only once
+ pub fn runs_once(&self) -> bool {
+ self.is_init() || self.is_idle()
+ }
+
+ /// Whether this context has shared resources
+ pub fn has_shared_resources(&self, app: &App) -> bool {
+ match *self {
+ Context::HardwareTask(name) => {
+ !app.hardware_tasks[name].args.shared_resources.is_empty()
+ }
+ Context::Idle => !app.idle.as_ref().unwrap().args.shared_resources.is_empty(),
+ Context::Init => false,
+ Context::SoftwareTask(name) => {
+ !app.software_tasks[name].args.shared_resources.is_empty()
+ }
+ }
+ }
+
+ /// Whether this context has local resources
+ pub fn has_local_resources(&self, app: &App) -> bool {
+ match *self {
+ Context::HardwareTask(name) => {
+ !app.hardware_tasks[name].args.local_resources.is_empty()
+ }
+ Context::Idle => !app.idle.as_ref().unwrap().args.local_resources.is_empty(),
+ Context::Init => !app.init.args.local_resources.is_empty(),
+ Context::SoftwareTask(name) => {
+ !app.software_tasks[name].args.local_resources.is_empty()
+ }
+ }
+ }
+}
+
+/// Parser and optimizer configuration
+#[derive(Default)]
+#[non_exhaustive]
+pub struct Settings {
+ /// Whether to accept the `binds` argument in `#[task]` or not
+ pub parse_binds: bool,
+ /// Whether to parse `extern` interrupts (functions) or not
+ pub parse_extern_interrupt: bool,
+ /// Whether to "compress" priorities or not
+ pub optimize_priorities: bool,
+}
+
+/// Parses the input of the `#[app]` attribute
+pub fn parse(
+ args: TokenStream,
+ input: TokenStream,
+ settings: Settings,
+) -> Result<(ast::App, analyze::Analysis), syn::parse::Error> {
+ parse2(args.into(), input.into(), settings)
+}
+
+/// `proc_macro2::TokenStream` version of `parse`
+pub fn parse2(
+ args: TokenStream2,
+ input: TokenStream2,
+ settings: Settings,
+) -> Result<(ast::App, analyze::Analysis), syn::parse::Error> {
+ let mut app = parse::app(args, input, &settings)?;
+ check::app(&app)?;
+ optimize::app(&mut app, &settings);
+
+ match analyze::app(&app) {
+ Err(e) => Err(e),
+ // If no errors, return the app and analysis results
+ Ok(analysis) => Ok((app, analysis)),
+ }
+}
+
+enum Either<A, B> {
+ Left(A),
+ Right(B),
+}
diff --git a/macros/src/syntax/.github/bors.toml b/macros/src/syntax/.github/bors.toml
new file mode 100644
index 00000000..aee6042f
--- /dev/null
+++ b/macros/src/syntax/.github/bors.toml
@@ -0,0 +1,3 @@
+block_labels = ["S-blocked"]
+delete_merged_branches = true
+status = ["ci"]
diff --git a/macros/src/syntax/.github/workflows/build.yml b/macros/src/syntax/.github/workflows/build.yml
new file mode 100644
index 00000000..29971b10
--- /dev/null
+++ b/macros/src/syntax/.github/workflows/build.yml
@@ -0,0 +1,213 @@
+name: Build
+on:
+ pull_request:
+ push:
+ branches:
+ - master
+ - staging
+ - trying
+ - bors/staging
+ - bors/trying
+
+env:
+ CARGO_TERM_COLOR: always
+
+jobs:
+ # Run cargo fmt --check
+ style:
+ name: style
+ runs-on: ubuntu-20.04
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+
+ - name: Install Rust
+ uses: actions-rs/toolchain@v1
+ with:
+ profile: minimal
+ toolchain: stable
+ override: true
+ components: rustfmt
+
+ - name: Fail on warnings
+ run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs
+
+ - name: cargo fmt --check
+ uses: actions-rs/cargo@v1
+ with:
+ command: fmt
+ args: --all -- --check
+
+ # Compilation check
+ check:
+ name: check
+ runs-on: ubuntu-20.04
+ strategy:
+ matrix:
+ target:
+ - x86_64-unknown-linux-gnu
+ toolchain:
+ - stable
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+
+ - name: Install Rust ${{ matrix.toolchain }} with target (${{ matrix.target }})
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: ${{ matrix.toolchain }}
+ target: ${{ matrix.target }}
+ override: true
+
+ - name: Fail on warnings
+ run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs
+
+ - name: Cache Dependencies
+ uses: Swatinem/rust-cache@v1
+
+ - name: cargo check
+ uses: actions-rs/cargo@v1
+ with:
+ use-cross: false
+ command: check
+ args: --target=${{ matrix.target }}
+
+ # Clippy
+ clippy:
+ name: Cargo clippy
+ runs-on: ubuntu-20.04
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+
+ - name: Install Rust stable
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: stable
+ target: x86_64-unknown-linux-gnu
+ override: true
+
+ - name: Fail on warnings
+ run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs
+
+ - name: Cache Dependencies
+ uses: Swatinem/rust-cache@v1
+
+ - name: cargo clippy
+ uses: actions-rs/cargo@v1
+ with:
+ use-cross: false
+ command: clippy
+
+ # Verify all examples
+ testexamples:
+ name: testexamples
+ runs-on: ubuntu-20.04
+ strategy:
+ matrix:
+ target:
+ - x86_64-unknown-linux-gnu
+ toolchain:
+ - stable
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+
+ - name: Install Rust ${{ matrix.toolchain }} with target (${{ matrix.target }})
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: ${{ matrix.toolchain }}
+ target: ${{ matrix.target }}
+ override: true
+
+ - name: Cache Dependencies
+ uses: Swatinem/rust-cache@v1
+
+ - name: Fail on warnings
+ run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs
+
+ - uses: actions-rs/cargo@v1
+ with:
+ use-cross: false
+ command: test
+ args: --examples
+
+ # Run test suite for UI
+ testui:
+ name: testui
+ runs-on: ubuntu-20.04
+ strategy:
+ matrix:
+ target:
+ - x86_64-unknown-linux-gnu
+ toolchain:
+ - stable
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+
+ - name: Install Rust ${{ matrix.toolchain }} with target (${{ matrix.target }})
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: ${{ matrix.toolchain }}
+ target: ${{ matrix.target }}
+ override: true
+
+ - name: Cache Dependencies
+ uses: Swatinem/rust-cache@v1
+
+ - name: Fail on warnings
+ run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs
+
+
+ - uses: actions-rs/cargo@v1
+ with:
+ use-cross: false
+ command: test
+ args: --test ui
+
+ # Run test suite
+ test:
+ name: test
+ runs-on: ubuntu-20.04
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+
+ - name: Install Rust
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: stable
+ target: thumbv7m-none-eabi
+ override: true
+
+ - name: Cache Dependencies
+ uses: Swatinem/rust-cache@v1
+
+ - name: Fail on warnings
+ run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs
+
+ - uses: actions-rs/cargo@v1
+ with:
+ use-cross: false
+ command: test
+ args: --lib
+
+ # Refs: https://github.com/rust-lang/crater/blob/9ab6f9697c901c4a44025cf0a39b73ad5b37d198/.github/workflows/bors.yml#L125-L149
+ #
+ # ALL THE PREVIOUS JOBS NEEDS TO BE ADDED TO THE `needs` SECTION OF THIS JOB!
+
+ ci-success:
+ name: ci
+ if: github.event_name == 'push' && success()
+ needs:
+ - style
+ - check
+ - clippy
+ - testexamples
+ - test
+ - testui
+ runs-on: ubuntu-20.04
+ steps:
+ - name: Mark the job as a success
+ run: exit 0
diff --git a/macros/src/syntax/.github/workflows/changelog.yml b/macros/src/syntax/.github/workflows/changelog.yml
new file mode 100644
index 00000000..ccf6eb91
--- /dev/null
+++ b/macros/src/syntax/.github/workflows/changelog.yml
@@ -0,0 +1,28 @@
+# Check that the changelog is updated for all changes.
+#
+# This is only run for PRs.
+
+on:
+ pull_request:
+ # opened, reopened, synchronize are the default types for pull_request.
+ # labeled, unlabeled ensure this check is also run if a label is added or removed.
+ types: [opened, reopened, labeled, unlabeled, synchronize]
+
+name: Changelog
+
+jobs:
+ changelog:
+ name: Changelog
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout sources
+ uses: actions/checkout@v2
+
+ - name: Check that changelog updated
+ uses: dangoslen/changelog-enforcer@v3
+ with:
+ changeLogPath: CHANGELOG.md
+ skipLabels: 'needs-changelog, skip-changelog'
+ missingUpdateErrorMessage: 'Please add a changelog entry in the CHANGELOG.md file.'
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file
diff --git a/macros/src/syntax/.github/workflows/properties/build.properties.json b/macros/src/syntax/.github/workflows/properties/build.properties.json
new file mode 100644
index 00000000..fd3eed37
--- /dev/null
+++ b/macros/src/syntax/.github/workflows/properties/build.properties.json
@@ -0,0 +1,6 @@
+{
+ "name": "Build",
+ "description": "RTIC Test Suite",
+ "iconName": "rust",
+ "categories": ["Rust"]
+}
diff --git a/macros/src/syntax/.gitignore b/macros/src/syntax/.gitignore
new file mode 100644
index 00000000..f8d7c8b4
--- /dev/null
+++ b/macros/src/syntax/.gitignore
@@ -0,0 +1,4 @@
+**/*.rs.bk
+.#*
+/target/
+Cargo.lock
diff --git a/macros/src/syntax/.travis.yml b/macros/src/syntax/.travis.yml
new file mode 100644
index 00000000..52d1ffdd
--- /dev/null
+++ b/macros/src/syntax/.travis.yml
@@ -0,0 +1,31 @@
+language: rust
+
+matrix:
+ include:
+ # MSRV
+ - env: TARGET=x86_64-unknown-linux-gnu
+ rust: 1.36.0
+
+ - env: TARGET=x86_64-unknown-linux-gnu
+ rust: stable
+
+before_install: set -e
+
+script:
+ - bash ci/script.sh
+
+after_script: set +e
+
+cache: cargo
+
+before_cache:
+ - chmod -R a+r $HOME/.cargo;
+
+branches:
+ only:
+ - staging
+ - trying
+
+notifications:
+ email:
+ on_success: never
diff --git a/macros/src/syntax/accessors.rs b/macros/src/syntax/accessors.rs
new file mode 100644
index 00000000..e75dde6c
--- /dev/null
+++ b/macros/src/syntax/accessors.rs
@@ -0,0 +1,113 @@
+use syn::Ident;
+
+use crate::syntax::{
+ analyze::Priority,
+ ast::{Access, App, Local, TaskLocal},
+};
+
+impl App {
+ pub(crate) fn shared_resource_accesses(
+ &self,
+ ) -> impl Iterator<Item = (Option<Priority>, &Ident, Access)> {
+ self.idle
+ .iter()
+ .flat_map(|idle| {
+ idle.args
+ .shared_resources
+ .iter()
+ .map(move |(name, access)| (Some(0), name, *access))
+ })
+ .chain(self.hardware_tasks.values().flat_map(|task| {
+ task.args
+ .shared_resources
+ .iter()
+ .map(move |(name, access)| (Some(task.args.priority), name, *access))
+ }))
+ .chain(self.software_tasks.values().flat_map(|task| {
+ task.args
+ .shared_resources
+ .iter()
+ .map(move |(name, access)| (Some(task.args.priority), name, *access))
+ }))
+ }
+
+ fn is_external(task_local: &TaskLocal) -> bool {
+ matches!(task_local, TaskLocal::External)
+ }
+
+ pub(crate) fn local_resource_accesses(&self) -> impl Iterator<Item = &Ident> {
+ self.init
+ .args
+ .local_resources
+ .iter()
+ .filter(|(_, task_local)| Self::is_external(task_local)) // Only check the resources declared in `#[local]`
+ .map(move |(name, _)| name)
+ .chain(self.idle.iter().flat_map(|idle| {
+ idle.args
+ .local_resources
+ .iter()
+ .filter(|(_, task_local)| Self::is_external(task_local)) // Only check the resources declared in `#[local]`
+ .map(move |(name, _)| name)
+ }))
+ .chain(self.hardware_tasks.values().flat_map(|task| {
+ task.args
+ .local_resources
+ .iter()
+ .filter(|(_, task_local)| Self::is_external(task_local)) // Only check the resources declared in `#[local]`
+ .map(move |(name, _)| name)
+ }))
+ .chain(self.software_tasks.values().flat_map(|task| {
+ task.args
+ .local_resources
+ .iter()
+ .filter(|(_, task_local)| Self::is_external(task_local)) // Only check the resources declared in `#[local]`
+ .map(move |(name, _)| name)
+ }))
+ }
+
+ fn get_declared_local(tl: &TaskLocal) -> Option<&Local> {
+ match tl {
+ TaskLocal::External => None,
+ TaskLocal::Declared(l) => Some(l),
+ }
+ }
+
+ /// Get all declared local resources, i.e. `local = [NAME: TYPE = EXPR]`.
+ ///
+ /// Returns a vector of (task name, resource name, `Local` struct)
+ pub fn declared_local_resources(&self) -> Vec<(&Ident, &Ident, &Local)> {
+ self.init
+ .args
+ .local_resources
+ .iter()
+ .filter_map(move |(name, tl)| {
+ Self::get_declared_local(tl).map(|l| (&self.init.name, name, l))
+ })
+ .chain(self.idle.iter().flat_map(|idle| {
+ idle.args
+ .local_resources
+ .iter()
+ .filter_map(move |(name, tl)| {
+ Self::get_declared_local(tl)
+ .map(|l| (&self.idle.as_ref().unwrap().name, name, l))
+ })
+ }))
+ .chain(self.hardware_tasks.iter().flat_map(|(task_name, task)| {
+ task.args
+ .local_resources
+ .iter()
+ .filter_map(move |(name, tl)| {
+ Self::get_declared_local(tl).map(|l| (task_name, name, l))
+ })
+ }))
+ .chain(self.software_tasks.iter().flat_map(|(task_name, task)| {
+ task.args
+ .local_resources
+ .iter()
+ .filter_map(move |(name, tl)| {
+ Self::get_declared_local(tl).map(|l| (task_name, name, l))
+ })
+ }))
+ .collect()
+ }
+}
diff --git a/macros/src/syntax/analyze.rs b/macros/src/syntax/analyze.rs
new file mode 100644
index 00000000..06b23f46
--- /dev/null
+++ b/macros/src/syntax/analyze.rs
@@ -0,0 +1,448 @@
+//! RTIC application analysis
+
+use core::cmp;
+use std::collections::{BTreeMap, BTreeSet, HashMap};
+
+use indexmap::{IndexMap, IndexSet};
+use syn::{Ident, Type};
+
+use crate::syntax::{
+ ast::{App, LocalResources, TaskLocal},
+ Set,
+};
+
+pub(crate) fn app(app: &App) -> Result<Analysis, syn::Error> {
+ // Collect all tasks into a vector
+ type TaskName = String;
+ type Priority = u8;
+
+ // The task list is a Tuple (Name, Shared Resources, Local Resources, Priority, IsAsync)
+ let task_resources_list: Vec<(TaskName, Vec<&Ident>, &LocalResources, Priority, bool)> =
+ Some(&app.init)
+ .iter()
+ .map(|ht| {
+ (
+ "init".to_string(),
+ Vec::new(),
+ &ht.args.local_resources,
+ 0,
+ false,
+ )
+ })
+ .chain(app.idle.iter().map(|ht| {
+ (
+ "idle".to_string(),
+ ht.args
+ .shared_resources
+ .iter()
+ .map(|(v, _)| v)
+ .collect::<Vec<_>>(),
+ &ht.args.local_resources,
+ 0,
+ false,
+ )
+ }))
+ .chain(app.software_tasks.iter().map(|(name, ht)| {
+ (
+ name.to_string(),
+ ht.args
+ .shared_resources
+ .iter()
+ .map(|(v, _)| v)
+ .collect::<Vec<_>>(),
+ &ht.args.local_resources,
+ ht.args.priority,
+ ht.is_async,
+ )
+ }))
+ .chain(app.hardware_tasks.iter().map(|(name, ht)| {
+ (
+ name.to_string(),
+ ht.args
+ .shared_resources
+ .iter()
+ .map(|(v, _)| v)
+ .collect::<Vec<_>>(),
+ &ht.args.local_resources,
+ ht.args.priority,
+ false,
+ )
+ }))
+ .collect();
+
+ let mut error = vec![];
+ let mut lf_res_with_error = vec![];
+ let mut lf_hash = HashMap::new();
+
+ // Collect lock free resources
+ let lock_free: Vec<&Ident> = app
+ .shared_resources
+ .iter()
+ .filter(|(_, r)| r.properties.lock_free)
+ .map(|(i, _)| i)
+ .collect();
+
+ // Check that lock_free resources are correct
+ for lf_res in lock_free.iter() {
+ for (task, tr, _, priority, is_async) in task_resources_list.iter() {
+ for r in tr {
+ // Get all uses of resources annotated lock_free
+ if lf_res == r {
+ // lock_free resources are not allowed in async tasks
+ if *is_async {
+ error.push(syn::Error::new(
+ r.span(),
+ format!(
+ "Lock free shared resource {:?} is used by an async tasks, which is forbidden",
+ r.to_string(),
+ ),
+ ));
+ }
+
+ // HashMap returns the previous existing object if old.key == new.key
+ if let Some(lf_res) = lf_hash.insert(r.to_string(), (task, r, priority)) {
+ // Check if priority differ, if it does, append to
+ // list of resources which will be annotated with errors
+ if priority != lf_res.2 {
+ lf_res_with_error.push(lf_res.1);
+ lf_res_with_error.push(r);
+ }
+ // If the resource already violates lock free properties
+ if lf_res_with_error.contains(&r) {
+ lf_res_with_error.push(lf_res.1);
+ lf_res_with_error.push(r);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Add error message in the resource struct
+ for r in lock_free {
+ if lf_res_with_error.contains(&&r) {
+ error.push(syn::Error::new(
+ r.span(),
+ format!(
+ "Lock free shared resource {:?} is used by tasks at different priorities",
+ r.to_string(),
+ ),
+ ));
+ }
+ }
+
+ // Add error message for each use of the shared resource
+ for resource in lf_res_with_error.clone() {
+ error.push(syn::Error::new(
+ resource.span(),
+ format!(
+ "Shared resource {:?} is declared lock free but used by tasks at different priorities",
+ resource.to_string(),
+ ),
+ ));
+ }
+
+ // Collect local resources
+ let local: Vec<&Ident> = app.local_resources.iter().map(|(i, _)| i).collect();
+
+ let mut lr_with_error = vec![];
+ let mut lr_hash = HashMap::new();
+
+ // Check that local resources are not shared
+ for lr in local {
+ for (task, _, local_resources, _, _) in task_resources_list.iter() {
+ for (name, res) in local_resources.iter() {
+ // Get all uses of resources annotated lock_free
+ if lr == name {
+ match res {
+ TaskLocal::External => {
+ // HashMap returns the previous existing object if old.key == new.key
+ if let Some(lr) = lr_hash.insert(name.to_string(), (task, name)) {
+ lr_with_error.push(lr.1);
+ lr_with_error.push(name);
+ }
+ }
+ // If a declared local has the same name as the `#[local]` struct, it's an
+ // direct error
+ TaskLocal::Declared(_) => {
+ lr_with_error.push(lr);
+ lr_with_error.push(name);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Add error message for each use of the local resource
+ for resource in lr_with_error.clone() {
+ error.push(syn::Error::new(
+ resource.span(),
+ format!(
+ "Local resource {:?} is used by multiple tasks or collides with multiple definitions",
+ resource.to_string(),
+ ),
+ ));
+ }
+
+ // Check 0-priority async software tasks and idle dependency
+ for (name, task) in &app.software_tasks {
+ if task.args.priority == 0 {
+ // If there is a 0-priority task, there must be no idle
+ if app.idle.is_some() {
+ error.push(syn::Error::new(
+ name.span(),
+ format!(
+ "Software task {:?} has priority 0, but `#[idle]` is defined. 0-priority software tasks are only allowed if there is no `#[idle]`.",
+ name.to_string(),
+ )
+ ));
+ }
+
+ // 0-priority tasks must be async
+ if !task.is_async {
+ error.push(syn::Error::new(
+ name.span(),
+ format!(
+ "Software task {:?} has priority 0, but is not `async`. 0-priority software tasks must be `async`.",
+ name.to_string(),
+ )
+ ));
+ }
+ }
+ }
+
+ // Collect errors if any and return/halt
+ if !error.is_empty() {
+ let mut err = error.get(0).unwrap().clone();
+ error.iter().for_each(|e| err.combine(e.clone()));
+ return Err(err);
+ }
+
+ // e. Location of resources
+ let mut used_shared_resource = IndexSet::new();
+ let mut ownerships = Ownerships::new();
+ let mut sync_types = SyncTypes::new();
+ for (prio, name, access) in app.shared_resource_accesses() {
+ let res = app.shared_resources.get(name).expect("UNREACHABLE");
+
+ // (e)
+ // This shared resource is used
+ used_shared_resource.insert(name.clone());
+
+ // (c)
+ if let Some(priority) = prio {
+ if let Some(ownership) = ownerships.get_mut(name) {
+ match *ownership {
+ Ownership::Owned { priority: ceiling }
+ | Ownership::CoOwned { priority: ceiling }
+ | Ownership::Contended { ceiling }
+ if priority != ceiling =>
+ {
+ *ownership = Ownership::Contended {
+ ceiling: cmp::max(ceiling, priority),
+ };
+
+ if access.is_shared() {
+ sync_types.insert(res.ty.clone());
+ }
+ }
+
+ Ownership::Owned { priority: ceil } if ceil == priority => {
+ *ownership = Ownership::CoOwned { priority };
+ }
+
+ _ => {}
+ }
+ } else {
+ ownerships.insert(name.clone(), Ownership::Owned { priority });
+ }
+ }
+ }
+
+ // Create the list of used local resource Idents
+ let mut used_local_resource = IndexSet::new();
+
+ for (_, _, locals, _, _) in task_resources_list {
+ for (local, _) in locals {
+ used_local_resource.insert(local.clone());
+ }
+ }
+
+ // Most shared resources need to be `Send`
+ let mut send_types = SendTypes::new();
+ let owned_by_idle = Ownership::Owned { priority: 0 };
+ for (name, res) in app.shared_resources.iter() {
+ // Handle not owned by idle
+ if ownerships
+ .get(name)
+ .map(|ownership| *ownership != owned_by_idle)
+ .unwrap_or(false)
+ {
+ send_types.insert(res.ty.clone());
+ }
+ }
+
+ // Most local resources need to be `Send` as well
+ for (name, res) in app.local_resources.iter() {
+ if let Some(idle) = &app.idle {
+ // Only Send if not in idle or not at idle prio
+ if idle.args.local_resources.get(name).is_none()
+ && !ownerships
+ .get(name)
+ .map(|ownership| *ownership != owned_by_idle)
+ .unwrap_or(false)
+ {
+ send_types.insert(res.ty.clone());
+ }
+ } else {
+ send_types.insert(res.ty.clone());
+ }
+ }
+
+ let mut channels = Channels::new();
+
+ for (name, spawnee) in &app.software_tasks {
+ let spawnee_prio = spawnee.args.priority;
+
+ let channel = channels.entry(spawnee_prio).or_default();
+ channel.tasks.insert(name.clone());
+
+ if !spawnee.args.only_same_priority_spawn {
+ // Require `Send` if the task can be spawned from other priorities
+ spawnee.inputs.iter().for_each(|input| {
+ send_types.insert(input.ty.clone());
+ });
+ }
+ }
+
+ // No channel should ever be empty
+ debug_assert!(channels.values().all(|channel| !channel.tasks.is_empty()));
+
+ // Compute channel capacities
+ for channel in channels.values_mut() {
+ channel.capacity = channel
+ .tasks
+ .iter()
+ .map(|name| app.software_tasks[name].args.capacity)
+ .sum();
+ }
+
+ Ok(Analysis {
+ channels,
+ shared_resources: used_shared_resource,
+ local_resources: used_local_resource,
+ ownerships,
+ send_types,
+ sync_types,
+ })
+}
+
+/// Priority ceiling
+pub type Ceiling = Option<u8>;
+
+/// Task priority
+pub type Priority = u8;
+
+/// Resource name
+pub type Resource = Ident;
+
+/// Task name
+pub type Task = Ident;
+
+/// The result of analyzing an RTIC application
+pub struct Analysis {
+ /// SPSC message channels
+ pub channels: Channels,
+
+ /// Shared resources
+ ///
+ /// If a resource is not listed here it means that's a "dead" (never
+ /// accessed) resource and the backend should not generate code for it
+ pub shared_resources: UsedSharedResource,
+
+ /// Local resources
+ ///
+ /// If a resource is not listed here it means that's a "dead" (never
+ /// accessed) resource and the backend should not generate code for it
+ pub local_resources: UsedLocalResource,
+
+ /// Resource ownership
+ pub ownerships: Ownerships,
+
+ /// These types must implement the `Send` trait
+ pub send_types: SendTypes,
+
+ /// These types must implement the `Sync` trait
+ pub sync_types: SyncTypes,
+}
+
+/// All channels, keyed by dispatch priority
+pub type Channels = BTreeMap<Priority, Channel>;
+
+/// Location of all *used* shared resources
+pub type UsedSharedResource = IndexSet<Resource>;
+
+/// Location of all *used* local resources
+pub type UsedLocalResource = IndexSet<Resource>;
+
+/// Resource ownership
+pub type Ownerships = IndexMap<Resource, Ownership>;
+
+/// These types must implement the `Send` trait
+pub type SendTypes = Set<Box<Type>>;
+
+/// These types must implement the `Sync` trait
+pub type SyncTypes = Set<Box<Type>>;
+
+/// A channel used to send messages
+#[derive(Debug, Default)]
+pub struct Channel {
+ /// The channel capacity
+ pub capacity: u8,
+
+ /// Tasks that can be spawned on this channel
+ pub tasks: BTreeSet<Task>,
+}
+
+/// Resource ownership
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum Ownership {
+ /// Owned by a single task
+ Owned {
+ /// Priority of the task that owns this resource
+ priority: u8,
+ },
+
+ /// "Co-owned" by more than one task; all of them have the same priority
+ CoOwned {
+ /// Priority of the tasks that co-own this resource
+ priority: u8,
+ },
+
+ /// Contended by more than one task; the tasks have different priorities
+ Contended {
+ /// Priority ceiling
+ ceiling: u8,
+ },
+}
+
+impl Ownership {
+ /// Whether this resource needs to a lock at this priority level
+ pub fn needs_lock(&self, priority: u8) -> bool {
+ match self {
+ Ownership::Owned { .. } | Ownership::CoOwned { .. } => false,
+
+ Ownership::Contended { ceiling } => {
+ debug_assert!(*ceiling >= priority);
+
+ priority < *ceiling
+ }
+ }
+ }
+
+ /// Whether this resource is exclusively owned
+ pub fn is_owned(&self) -> bool {
+ matches!(self, Ownership::Owned { .. })
+ }
+}
diff --git a/macros/src/syntax/ast.rs b/macros/src/syntax/ast.rs
new file mode 100644
index 00000000..0f2e36f4
--- /dev/null
+++ b/macros/src/syntax/ast.rs
@@ -0,0 +1,380 @@
+//! Abstract Syntax Tree
+
+use syn::{Attribute, Expr, Ident, Item, ItemUse, Pat, PatType, Path, Stmt, Type};
+
+use crate::syntax::Map;
+
+/// The `#[app]` attribute
+#[derive(Debug)]
+#[non_exhaustive]
+pub struct App {
+ /// The arguments to the `#[app]` attribute
+ pub args: AppArgs,
+
+ /// The name of the `const` item on which the `#[app]` attribute has been placed
+ pub name: Ident,
+
+ /// The `#[init]` function
+ pub init: Init,
+
+ /// The `#[idle]` function
+ pub idle: Option<Idle>,
+
+ /// Monotonic clocks
+ pub monotonics: Map<Monotonic>,
+
+ /// Resources shared between tasks defined in `#[shared]`
+ pub shared_resources: Map<SharedResource>,
+
+ /// Task local resources defined in `#[local]`
+ pub local_resources: Map<LocalResource>,
+
+ /// User imports
+ pub user_imports: Vec<ItemUse>,
+
+ /// User code
+ pub user_code: Vec<Item>,
+
+ /// Hardware tasks: `#[task(binds = ..)]`s
+ pub hardware_tasks: Map<HardwareTask>,
+
+ /// Software tasks: `#[task]`
+ pub software_tasks: Map<SoftwareTask>,
+}
+
+/// Interrupts used to dispatch software tasks
+pub type Dispatchers = Map<Dispatcher>;
+
+/// Interrupt that could be used to dispatch software tasks
+#[derive(Debug, Clone)]
+#[non_exhaustive]
+pub struct Dispatcher {
+ /// Attributes that will apply to this interrupt handler
+ pub attrs: Vec<Attribute>,
+}
+
+/// The arguments of the `#[app]` attribute
+#[derive(Debug)]
+pub struct AppArgs {
+ /// Device
+ pub device: Path,
+
+ /// Peripherals
+ pub peripherals: bool,
+
+ /// Interrupts used to dispatch software tasks
+ pub dispatchers: Dispatchers,
+}
+
+/// The `init`-ialization function
+#[derive(Debug)]
+#[non_exhaustive]
+pub struct Init {
+ /// `init` context metadata
+ pub args: InitArgs,
+
+ /// Attributes that will apply to this `init` function
+ pub attrs: Vec<Attribute>,
+
+ /// The name of the `#[init]` function
+ pub name: Ident,
+
+ /// The context argument
+ pub context: Box<Pat>,
+
+ /// The statements that make up this `init` function
+ pub stmts: Vec<Stmt>,
+
+ /// The name of the user provided shared resources struct
+ pub user_shared_struct: Ident,
+
+ /// The name of the user provided local resources struct
+ pub user_local_struct: Ident,
+}
+
+/// `init` context metadata
+#[derive(Debug)]
+#[non_exhaustive]
+pub struct InitArgs {
+ /// Local resources that can be accessed from this context
+ pub local_resources: LocalResources,
+}
+
+impl Default for InitArgs {
+ fn default() -> Self {
+ Self {
+ local_resources: LocalResources::new(),
+ }
+ }
+}
+
+/// The `idle` context
+#[derive(Debug)]
+#[non_exhaustive]
+pub struct Idle {
+ /// `idle` context metadata
+ pub args: IdleArgs,
+
+ /// Attributes that will apply to this `idle` function
+ pub attrs: Vec<Attribute>,
+
+ /// The name of the `#[idle]` function
+ pub name: Ident,
+
+ /// The context argument
+ pub context: Box<Pat>,
+
+ /// The statements that make up this `idle` function
+ pub stmts: Vec<Stmt>,
+}
+
+/// `idle` context metadata
+#[derive(Debug)]
+#[non_exhaustive]
+pub struct IdleArgs {
+ /// Local resources that can be accessed from this context
+ pub local_resources: LocalResources,
+
+ /// Shared resources that can be accessed from this context
+ pub shared_resources: SharedResources,
+}
+
+impl Default for IdleArgs {
+ fn default() -> Self {
+ Self {
+ local_resources: LocalResources::new(),
+ shared_resources: SharedResources::new(),
+ }
+ }
+}
+
+/// Shared resource properties
+#[derive(Debug)]
+pub struct SharedResourceProperties {
+ /// A lock free (exclusive resource)
+ pub lock_free: bool,
+}
+
+/// A shared resource, defined in `#[shared]`
+#[derive(Debug)]
+#[non_exhaustive]
+pub struct SharedResource {
+ /// `#[cfg]` attributes like `#[cfg(debug_assertions)]`
+ pub cfgs: Vec<Attribute>,
+
+ /// `#[doc]` attributes like `/// this is a docstring`
+ pub docs: Vec<Attribute>,
+
+ /// Attributes that will apply to this resource
+ pub attrs: Vec<Attribute>,
+
+ /// The type of this resource
+ pub ty: Box<Type>,
+
+ /// Shared resource properties
+ pub properties: SharedResourceProperties,
+}
+
+/// A local resource, defined in `#[local]`
+#[derive(Debug)]
+#[non_exhaustive]
+pub struct LocalResource {
+ /// `#[cfg]` attributes like `#[cfg(debug_assertions)]`
+ pub cfgs: Vec<Attribute>,
+
+ /// `#[doc]` attributes like `/// this is a docstring`
+ pub docs: Vec<Attribute>,
+
+ /// Attributes that will apply to this resource
+ pub attrs: Vec<Attribute>,
+
+ /// The type of this resource
+ pub ty: Box<Type>,
+}
+
+/// Monotonic
+#[derive(Debug)]
+#[non_exhaustive]
+pub struct Monotonic {
+ /// `#[cfg]` attributes like `#[cfg(debug_assertions)]`
+ pub cfgs: Vec<Attribute>,
+
+ /// The identifier of the monotonic
+ pub ident: Ident,
+
+ /// The type of this monotonic
+ pub ty: Box<Type>,
+
+ /// Monotonic args
+ pub args: MonotonicArgs,
+}
+
+/// Monotonic metadata
+#[derive(Debug)]
+#[non_exhaustive]
+pub struct MonotonicArgs {
+ /// The interrupt or exception that this monotonic is bound to
+ pub binds: Ident,
+
+ /// The priority of this monotonic
+ pub priority: Option<u8>,
+
+ /// If this is the default monotonic
+ pub default: bool,
+}
+
+/// A software task
+#[derive(Debug)]
+#[non_exhaustive]
+pub struct SoftwareTask {
+ /// Software task metadata
+ pub args: SoftwareTaskArgs,
+
+ /// `#[cfg]` attributes like `#[cfg(debug_assertions)]`
+ pub cfgs: Vec<Attribute>,
+
+ /// Attributes that will apply to this interrupt handler
+ pub attrs: Vec<Attribute>,
+
+ /// The context argument
+ pub context: Box<Pat>,
+
+ /// The inputs of this software task
+ pub inputs: Vec<PatType>,
+
+ /// The statements that make up the task handler
+ pub stmts: Vec<Stmt>,
+
+ /// The task is declared externally
+ pub is_extern: bool,
+
+ /// If the task is marked as `async`
+ pub is_async: bool,
+}
+
+/// Software task metadata
+#[derive(Debug)]
+#[non_exhaustive]
+pub struct SoftwareTaskArgs {
+ /// The task capacity: the maximum number of pending messages that can be queued
+ pub capacity: u8,
+
+ /// The priority of this task
+ pub priority: u8,
+
+ /// Local resources that can be accessed from this context
+ pub local_resources: LocalResources,
+
+ /// Shared resources that can be accessed from this context
+ pub shared_resources: SharedResources,
+
+ /// Only same priority tasks can spawn this task
+ pub only_same_priority_spawn: bool,
+}
+
+impl Default for SoftwareTaskArgs {
+ fn default() -> Self {
+ Self {
+ capacity: 1,
+ priority: 1,
+ local_resources: LocalResources::new(),
+ shared_resources: SharedResources::new(),
+ only_same_priority_spawn: false,
+ }
+ }
+}
+
+/// A hardware task
+#[derive(Debug)]
+#[non_exhaustive]
+pub struct HardwareTask {
+ /// Hardware task metadata
+ pub args: HardwareTaskArgs,
+
+ /// `#[cfg]` attributes like `#[cfg(debug_assertions)]`
+ pub cfgs: Vec<Attribute>,
+
+ /// Attributes that will apply to this interrupt handler
+ pub attrs: Vec<Attribute>,
+
+ /// The context argument
+ pub context: Box<Pat>,
+
+ /// The statements that make up the task handler
+ pub stmts: Vec<Stmt>,
+
+ /// The task is declared externally
+ pub is_extern: bool,
+}
+
+/// Hardware task metadata
+#[derive(Debug)]
+#[non_exhaustive]
+pub struct HardwareTaskArgs {
+ /// The interrupt or exception that this task is bound to
+ pub binds: Ident,
+
+ /// The priority of this task
+ pub priority: u8,
+
+ /// Local resources that can be accessed from this context
+ pub local_resources: LocalResources,
+
+ /// Shared resources that can be accessed from this context
+ pub shared_resources: SharedResources,
+}
+
+/// A `static mut` variable local to and owned by a context
+#[derive(Debug)]
+#[non_exhaustive]
+pub struct Local {
+ /// Attributes like `#[link_section]`
+ pub attrs: Vec<Attribute>,
+
+ /// `#[cfg]` attributes like `#[cfg(debug_assertions)]`
+ pub cfgs: Vec<Attribute>,
+
+ /// Type
+ pub ty: Box<Type>,
+
+ /// Initial value
+ pub expr: Box<Expr>,
+}
+
+/// A wrapper of the 2 kinds of locals that tasks can have
+#[derive(Debug)]
+#[non_exhaustive]
+pub enum TaskLocal {
+ /// The local is declared externally (i.e. `#[local]` struct)
+ External,
+ /// The local is declared in the task
+ Declared(Local),
+}
+
+/// Resource access
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum Access {
+ /// `[x]`, a mutable resource
+ Exclusive,
+
+ /// `[&x]`, a static non-mutable resource
+ Shared,
+}
+
+impl Access {
+ /// Is this enum in the `Exclusive` variant?
+ pub fn is_exclusive(&self) -> bool {
+ *self == Access::Exclusive
+ }
+
+ /// Is this enum in the `Shared` variant?
+ pub fn is_shared(&self) -> bool {
+ *self == Access::Shared
+ }
+}
+
+/// Shared resource access list in task attribute
+pub type SharedResources = Map<Access>;
+
+/// Local resource access/declaration list in task attribute
+pub type LocalResources = Map<TaskLocal>;
diff --git a/macros/src/syntax/check.rs b/macros/src/syntax/check.rs
new file mode 100644
index 00000000..989d4180
--- /dev/null
+++ b/macros/src/syntax/check.rs
@@ -0,0 +1,66 @@
+use std::collections::HashSet;
+
+use syn::parse;
+
+use crate::syntax::ast::App;
+
+pub fn app(app: &App) -> parse::Result<()> {
+ // Check that all referenced resources have been declared
+ // Check that resources are NOT `Exclusive`-ly shared
+ let mut owners = HashSet::new();
+ for (_, name, access) in app.shared_resource_accesses() {
+ if app.shared_resources.get(name).is_none() {
+ return Err(parse::Error::new(
+ name.span(),
+ "this shared resource has NOT been declared",
+ ));
+ }
+
+ if access.is_exclusive() {
+ owners.insert(name);
+ }
+ }
+
+ for name in app.local_resource_accesses() {
+ if app.local_resources.get(name).is_none() {
+ return Err(parse::Error::new(
+ name.span(),
+ "this local resource has NOT been declared",
+ ));
+ }
+ }
+
+ // Check that no resource has both types of access (`Exclusive` & `Shared`)
+ let exclusive_accesses = app
+ .shared_resource_accesses()
+ .filter_map(|(priority, name, access)| {
+ if priority.is_some() && access.is_exclusive() {
+ Some(name)
+ } else {
+ None
+ }
+ })
+ .collect::<HashSet<_>>();
+ for (_, name, access) in app.shared_resource_accesses() {
+ if access.is_shared() && exclusive_accesses.contains(name) {
+ return Err(parse::Error::new(
+ name.span(),
+ "this implementation doesn't support shared (`&-`) - exclusive (`&mut-`) locks; use `x` instead of `&x`",
+ ));
+ }
+ }
+
+ // check that dispatchers are not used as hardware tasks
+ for task in app.hardware_tasks.values() {
+ let binds = &task.args.binds;
+
+ if app.args.dispatchers.contains_key(binds) {
+ return Err(parse::Error::new(
+ binds.span(),
+ "dispatcher interrupts can't be used as hardware tasks",
+ ));
+ }
+ }
+
+ Ok(())
+}
diff --git a/macros/src/syntax/optimize.rs b/macros/src/syntax/optimize.rs
new file mode 100644
index 00000000..87a6258d
--- /dev/null
+++ b/macros/src/syntax/optimize.rs
@@ -0,0 +1,36 @@
+use std::collections::{BTreeSet, HashMap};
+
+use crate::syntax::{ast::App, Settings};
+
+pub fn app(app: &mut App, settings: &Settings) {
+ // "compress" priorities
+ // If the user specified, for example, task priorities of "1, 3, 6",
+ // compress them into "1, 2, 3" as to leave no gaps
+ if settings.optimize_priorities {
+ // all task priorities ordered in ascending order
+ let priorities = app
+ .hardware_tasks
+ .values()
+ .map(|task| Some(task.args.priority))
+ .chain(
+ app.software_tasks
+ .values()
+ .map(|task| Some(task.args.priority)),
+ )
+ .collect::<BTreeSet<_>>();
+
+ let map = priorities
+ .iter()
+ .cloned()
+ .zip(1..)
+ .collect::<HashMap<_, _>>();
+
+ for task in app.hardware_tasks.values_mut() {
+ task.args.priority = map[&Some(task.args.priority)];
+ }
+
+ for task in app.software_tasks.values_mut() {
+ task.args.priority = map[&Some(task.args.priority)];
+ }
+ }
+}
diff --git a/macros/src/syntax/parse.rs b/macros/src/syntax/parse.rs
new file mode 100644
index 00000000..74f94f2b
--- /dev/null
+++ b/macros/src/syntax/parse.rs
@@ -0,0 +1,520 @@
+mod app;
+mod hardware_task;
+mod idle;
+mod init;
+mod monotonic;
+mod resource;
+mod software_task;
+mod util;
+
+use proc_macro2::TokenStream as TokenStream2;
+use syn::{
+ braced, parenthesized,
+ parse::{self, Parse, ParseStream, Parser},
+ token::{self, Brace},
+ Ident, Item, LitBool, LitInt, Path, Token,
+};
+
+use crate::syntax::{
+ ast::{
+ App, AppArgs, HardwareTaskArgs, IdleArgs, InitArgs, MonotonicArgs, SoftwareTaskArgs,
+ TaskLocal,
+ },
+ Either, Settings,
+};
+
+// Parse the app, both app arguments and body (input)
+pub fn app(args: TokenStream2, input: TokenStream2, settings: &Settings) -> parse::Result<App> {
+ let args = AppArgs::parse(args)?;
+ let input: Input = syn::parse2(input)?;
+
+ App::parse(args, input, settings)
+}
+
+pub(crate) struct Input {
+ _mod_token: Token![mod],
+ pub ident: Ident,
+ _brace_token: Brace,
+ pub items: Vec<Item>,
+}
+
+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;
+
+ let _mod_token = input.parse()?;
+ let ident = input.parse()?;
+ let _brace_token = braced!(content in input);
+ let items = content.call(parse_items)?;
+
+ Ok(Input {
+ _mod_token,
+ ident,
+ _brace_token,
+ items,
+ })
+ }
+}
+
+fn init_args(tokens: TokenStream2) -> parse::Result<InitArgs> {
+ (|input: ParseStream<'_>| -> parse::Result<InitArgs> {
+ if input.is_empty() {
+ return Ok(InitArgs::default());
+ }
+
+ let mut local_resources = None;
+
+ let content;
+ parenthesized!(content in input);
+
+ if !content.is_empty() {
+ loop {
+ // Parse identifier name
+ let ident: Ident = content.parse()?;
+ // Handle equal sign
+ let _: Token![=] = content.parse()?;
+
+ match &*ident.to_string() {
+ "local" => {
+ if local_resources.is_some() {
+ return Err(parse::Error::new(
+ ident.span(),
+ "argument appears more than once",
+ ));
+ }
+
+ local_resources = Some(util::parse_local_resources(&content)?);
+ }
+ _ => {
+ return Err(parse::Error::new(ident.span(), "unexpected argument"));
+ }
+ }
+
+ if content.is_empty() {
+ break;
+ }
+ // Handle comma: ,
+ let _: Token![,] = content.parse()?;
+ }
+ }
+
+ if let Some(locals) = &local_resources {
+ for (ident, task_local) in locals {
+ if let TaskLocal::External = task_local {
+ return Err(parse::Error::new(
+ ident.span(),
+ "only declared local resources are allowed in init",
+ ));
+ }
+ }
+ }
+
+ Ok(InitArgs {
+ local_resources: local_resources.unwrap_or_default(),
+ })
+ })
+ .parse2(tokens)
+}
+
+fn idle_args(tokens: TokenStream2) -> parse::Result<IdleArgs> {
+ (|input: ParseStream<'_>| -> parse::Result<IdleArgs> {
+ if input.is_empty() {
+ return Ok(IdleArgs::default());
+ }
+
+ let mut shared_resources = None;
+ let mut local_resources = None;
+
+ let content;
+ parenthesized!(content in input);
+ if !content.is_empty() {
+ loop {
+ // Parse identifier name
+ let ident: Ident = content.parse()?;
+ // Handle equal sign
+ let _: Token![=] = content.parse()?;
+
+ match &*ident.to_string() {
+ "shared" => {
+ if shared_resources.is_some() {
+ return Err(parse::Error::new(
+ ident.span(),
+ "argument appears more than once",
+ ));
+ }
+
+ shared_resources = Some(util::parse_shared_resources(&content)?);
+ }
+
+ "local" => {
+ if local_resources.is_some() {
+ return Err(parse::Error::new(
+ ident.span(),
+ "argument appears more than once",
+ ));
+ }
+
+ local_resources = Some(util::parse_local_resources(&content)?);
+ }
+
+ _ => {
+ return Err(parse::Error::new(ident.span(), "unexpected argument"));
+ }
+ }
+ if content.is_empty() {
+ break;
+ }
+
+ // Handle comma: ,
+ let _: Token![,] = content.parse()?;
+ }
+ }
+
+ Ok(IdleArgs {
+ shared_resources: shared_resources.unwrap_or_default(),
+ local_resources: local_resources.unwrap_or_default(),
+ })
+ })
+ .parse2(tokens)
+}
+
+fn task_args(
+ tokens: TokenStream2,
+ settings: &Settings,
+) -> parse::Result<Either<HardwareTaskArgs, SoftwareTaskArgs>> {
+ (|input: ParseStream<'_>| -> parse::Result<Either<HardwareTaskArgs, SoftwareTaskArgs>> {
+ if input.is_empty() {
+ return Ok(Either::Right(SoftwareTaskArgs::default()));
+ }
+
+ let mut binds = None;
+ let mut capacity = None;
+ let mut priority = None;
+ let mut shared_resources = None;
+ let mut local_resources = None;
+ let mut prio_span = None;
+ let mut only_same_priority_spawn = false;
+ let mut only_same_prio_span = None;
+
+ let content;
+ parenthesized!(content in input);
+ loop {
+ if content.is_empty() {
+ break;
+ }
+
+ // Parse identifier name
+ let ident: Ident = content.parse()?;
+ let ident_s = ident.to_string();
+
+ if ident_s == "only_same_priority_spawn_please_fix_me" {
+ if only_same_priority_spawn {
+ return Err(parse::Error::new(
+ ident.span(),
+ "argument appears more than once",
+ ));
+ }
+
+ only_same_priority_spawn = true;
+ only_same_prio_span = Some(ident.span());
+
+ if content.is_empty() {
+ break;
+ }
+
+ // Handle comma: ,
+ let _: Token![,] = content.parse()?;
+
+ continue;
+ }
+
+ // Handle equal sign
+ let _: Token![=] = content.parse()?;
+
+ match &*ident_s {
+ "binds" if !settings.parse_binds => {
+ return Err(parse::Error::new(
+ ident.span(),
+ "Unexpected bind in task argument. Binds are only parsed if Settings::parse_binds is set.",
+ ));
+ }
+
+ "binds" if settings.parse_binds => {
+ if binds.is_some() {
+ return Err(parse::Error::new(
+ ident.span(),
+ "argument appears more than once",
+ ));
+ }
+
+ if capacity.is_some() {
+ return Err(parse::Error::new(
+ ident.span(),
+ "hardware tasks can't use the `capacity` argument",
+ ));
+ }
+
+ // Parse identifier name
+ let ident = content.parse()?;
+
+ binds = Some(ident);
+ }
+
+ "capacity" => {
+ if capacity.is_some() {
+ return Err(parse::Error::new(
+ ident.span(),
+ "argument appears more than once",
+ ));
+ }
+
+ if binds.is_some() {
+ return Err(parse::Error::new(
+ ident.span(),
+ "hardware tasks can't use the `capacity` argument",
+ ));
+ }
+
+ // #lit
+ let lit: LitInt = content.parse()?;
+
+ if !lit.suffix().is_empty() {
+ return Err(parse::Error::new(
+ lit.span(),
+ "this literal must be unsuffixed",
+ ));
+ }
+
+ let value = lit.base10_parse::<u8>().ok();
+ if value.is_none() || value == Some(0) {
+ return Err(parse::Error::new(
+ lit.span(),
+ "this literal must be in the range 1...255",
+ ));
+ }
+
+ capacity = Some(value.unwrap());
+ }
+
+ "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().is_empty() {
+ return Err(parse::Error::new(
+ lit.span(),
+ "this literal must be unsuffixed",
+ ));
+ }
+
+ let value = lit.base10_parse::<u8>().ok();
+ if value.is_none() {
+ return Err(parse::Error::new(
+ lit.span(),
+ "this literal must be in the range 0...255",
+ ));
+ }
+
+ prio_span = Some(lit.span());
+ priority = Some(value.unwrap());
+ }
+
+ "shared" => {
+ if shared_resources.is_some() {
+ return Err(parse::Error::new(
+ ident.span(),
+ "argument appears more than once",
+ ));
+ }
+
+ shared_resources = Some(util::parse_shared_resources(&content)?);
+ }
+
+ "local" => {
+ if local_resources.is_some() {
+ return Err(parse::Error::new(
+ ident.span(),
+ "argument appears more than once",
+ ));
+ }
+
+ local_resources = Some(util::parse_local_resources(&content)?);
+ }
+
+
+ _ => {
+ return Err(parse::Error::new(ident.span(), "unexpected argument"));
+ }
+ }
+
+ if content.is_empty() {
+ break;
+ }
+
+ // Handle comma: ,
+ let _: Token![,] = content.parse()?;
+ }
+ let priority = priority.unwrap_or(1);
+ let shared_resources = shared_resources.unwrap_or_default();
+ let local_resources = local_resources.unwrap_or_default();
+
+ Ok(if let Some(binds) = binds {
+ if priority == 0 {
+ return Err(parse::Error::new(
+ prio_span.unwrap(),
+ "hardware tasks are not allowed to be at priority 0",
+ ));
+ }
+
+ if only_same_priority_spawn {
+ return Err(parse::Error::new(
+ only_same_prio_span.unwrap(),
+ "hardware tasks are not allowed to be spawned, `only_same_priority_spawn_please_fix_me` is only for software tasks",
+ ));
+ }
+
+ Either::Left(HardwareTaskArgs {
+ binds,
+ priority,
+ shared_resources,
+ local_resources,
+ })
+ } else {
+ Either::Right(SoftwareTaskArgs {
+ capacity: capacity.unwrap_or(1),
+ priority,
+ shared_resources,
+ local_resources,
+ only_same_priority_spawn,
+ })
+ })
+ })
+ .parse2(tokens)
+}
+
+fn monotonic_args(path: Path, tokens: TokenStream2) -> parse::Result<MonotonicArgs> {
+ (|input: ParseStream<'_>| -> parse::Result<MonotonicArgs> {
+ let mut binds = None;
+ let mut priority = None;
+ let mut default = None;
+
+ if !input.peek(token::Paren) {
+ return Err(parse::Error::new(
+ path.segments.first().unwrap().ident.span(),
+ "expected opening ( in #[monotonic( ... )]",
+ ));
+ }
+
+ let content;
+ parenthesized!(content in input);
+
+ if !content.is_empty() {
+ loop {
+ // Parse identifier name
+ let ident: Ident = content.parse()?;
+ // Handle equal sign
+ let _: Token![=] = content.parse()?;
+
+ match &*ident.to_string() {
+ "binds" => {
+ if binds.is_some() {
+ return Err(parse::Error::new(
+ ident.span(),
+ "argument appears more than once",
+ ));
+ }
+ // Parse identifier name
+ let ident = content.parse()?;
+
+ binds = Some(ident);
+ }
+
+ "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().is_empty() {
+ return Err(parse::Error::new(
+ lit.span(),
+ "this literal must be unsuffixed",
+ ));
+ }
+
+ let value = lit.base10_parse::<u8>().ok();
+ if value.is_none() || value == Some(0) {
+ return Err(parse::Error::new(
+ lit.span(),
+ "this literal must be in the range 1...255",
+ ));
+ }
+
+ priority = Some(value.unwrap());
+ }
+
+ "default" => {
+ if default.is_some() {
+ return Err(parse::Error::new(
+ ident.span(),
+ "argument appears more than once",
+ ));
+ }
+
+ let lit: LitBool = content.parse()?;
+ default = Some(lit.value);
+ }
+
+ _ => {
+ return Err(parse::Error::new(ident.span(), "unexpected argument"));
+ }
+ }
+ if content.is_empty() {
+ break;
+ }
+
+ // Handle comma: ,
+ let _: Token![,] = content.parse()?;
+ }
+ }
+
+ let binds = if let Some(r) = binds {
+ r
+ } else {
+ return Err(parse::Error::new(
+ content.span(),
+ "`binds = ...` is missing",
+ ));
+ };
+ let default = default.unwrap_or(false);
+
+ Ok(MonotonicArgs {
+ binds,
+ priority,
+ default,
+ })
+ })
+ .parse2(tokens)
+}
diff --git a/macros/src/syntax/parse/app.rs b/macros/src/syntax/parse/app.rs
new file mode 100644
index 00000000..7eb415d3
--- /dev/null
+++ b/macros/src/syntax/parse/app.rs
@@ -0,0 +1,539 @@
+use std::collections::HashSet;
+
+// use indexmap::map::Entry;
+use proc_macro2::TokenStream as TokenStream2;
+use syn::{
+ parse::{self, ParseStream, Parser},
+ spanned::Spanned,
+ Expr, ExprArray, Fields, ForeignItem, Ident, Item, LitBool, Path, Token, Type, Visibility,
+};
+
+use super::Input;
+use crate::syntax::{
+ ast::{
+ App, AppArgs, Dispatcher, Dispatchers, HardwareTask, Idle, IdleArgs, Init, InitArgs,
+ LocalResource, Monotonic, MonotonicArgs, SharedResource, SoftwareTask,
+ },
+ parse::{self as syntax_parse, util},
+ Either, Map, Set, Settings,
+};
+
+impl AppArgs {
+ pub(crate) fn parse(tokens: TokenStream2) -> parse::Result<Self> {
+ (|input: ParseStream<'_>| -> parse::Result<Self> {
+ let mut custom = Set::new();
+ let mut device = None;
+ let mut peripherals = true;
+ let mut dispatchers = Dispatchers::new();
+
+ loop {
+ if input.is_empty() {
+ break;
+ }
+
+ // #ident = ..
+ let ident: Ident = input.parse()?;
+ let _eq_token: Token![=] = input.parse()?;
+
+ if custom.contains(&ident) {
+ return Err(parse::Error::new(
+ ident.span(),
+ "argument appears more than once",
+ ));
+ }
+
+ custom.insert(ident.clone());
+
+ let ks = ident.to_string();
+
+ match &*ks {
+ "device" => {
+ if let Ok(p) = input.parse::<Path>() {
+ device = Some(p);
+ } else {
+ return Err(parse::Error::new(
+ ident.span(),
+ "unexpected argument value; this should be a path",
+ ));
+ }
+ }
+
+ "peripherals" => {
+ if let Ok(p) = input.parse::<LitBool>() {
+ peripherals = p.value;
+ } else {
+ return Err(parse::Error::new(
+ ident.span(),
+ "unexpected argument value; this should be a boolean",
+ ));
+ }
+ }
+
+ "dispatchers" => {
+ if let Ok(p) = input.parse::<ExprArray>() {
+ for e in p.elems {
+ match e {
+ Expr::Path(ep) => {
+ let path = ep.path;
+ let ident = if path.leading_colon.is_some()
+ || path.segments.len() != 1
+ {
+ return Err(parse::Error::new(
+ path.span(),
+ "interrupt must be an identifier, not a path",
+ ));
+ } else {
+ path.segments[0].ident.clone()
+ };
+ let span = ident.span();
+ if dispatchers.contains_key(&ident) {
+ return Err(parse::Error::new(
+ span,
+ "this extern interrupt is listed more than once",
+ ));
+ } else {
+ dispatchers
+ .insert(ident, Dispatcher { attrs: ep.attrs });
+ }
+ }
+ _ => {
+ return Err(parse::Error::new(
+ e.span(),
+ "interrupt must be an identifier",
+ ));
+ }
+ }
+ }
+ } else {
+ return Err(parse::Error::new(
+ ident.span(),
+ // increasing the length of the error message will break rustfmt
+ "unexpected argument value; expected an array",
+ ));
+ }
+ }
+ _ => {
+ return Err(parse::Error::new(ident.span(), "unexpected argument"));
+ }
+ }
+
+ if input.is_empty() {
+ break;
+ }
+
+ // ,
+ let _: Token![,] = input.parse()?;
+ }
+
+ let device = if let Some(device) = device {
+ device
+ } else {
+ return Err(parse::Error::new(input.span(), "missing `device = ...`"));
+ };
+
+ Ok(AppArgs {
+ device,
+ peripherals,
+ dispatchers,
+ })
+ })
+ .parse2(tokens)
+ }
+}
+
+impl App {
+ pub(crate) fn parse(args: AppArgs, input: Input, settings: &Settings) -> parse::Result<Self> {
+ let mut init = None;
+ let mut idle = None;
+
+ let mut shared_resources_ident = None;
+ let mut shared_resources = Map::new();
+ let mut local_resources_ident = None;
+ let mut local_resources = Map::new();
+ let mut monotonics = Map::new();
+ let mut hardware_tasks = Map::new();
+ let mut software_tasks = Map::new();
+ let mut user_imports = vec![];
+ let mut user_code = vec![];
+
+ let mut seen_idents = HashSet::<Ident>::new();
+ let mut bindings = HashSet::<Ident>::new();
+ let mut monotonic_types = HashSet::<Type>::new();
+
+ let mut check_binding = |ident: &Ident| {
+ if bindings.contains(ident) {
+ return Err(parse::Error::new(
+ ident.span(),
+ "this interrupt is already bound",
+ ));
+ } else {
+ bindings.insert(ident.clone());
+ }
+
+ Ok(())
+ };
+
+ let mut check_ident = |ident: &Ident| {
+ if seen_idents.contains(ident) {
+ return Err(parse::Error::new(
+ ident.span(),
+ "this identifier has already been used",
+ ));
+ } else {
+ seen_idents.insert(ident.clone());
+ }
+
+ Ok(())
+ };
+
+ let mut check_monotonic = |ty: &Type| {
+ if monotonic_types.contains(ty) {
+ return Err(parse::Error::new(
+ ty.span(),
+ "this type is already used by another monotonic",
+ ));
+ } else {
+ monotonic_types.insert(ty.clone());
+ }
+
+ Ok(())
+ };
+
+ for mut item in input.items {
+ match item {
+ Item::Fn(mut item) => {
+ let span = item.sig.ident.span();
+ if let Some(pos) = item
+ .attrs
+ .iter()
+ .position(|attr| util::attr_eq(attr, "init"))
+ {
+ let args = InitArgs::parse(item.attrs.remove(pos).tokens)?;
+
+ // If an init function already exists, error
+ if init.is_some() {
+ return Err(parse::Error::new(
+ span,
+ "`#[init]` function must appear at most once",
+ ));
+ }
+
+ check_ident(&item.sig.ident)?;
+
+ init = Some(Init::parse(args, item)?);
+ } else if let Some(pos) = item
+ .attrs
+ .iter()
+ .position(|attr| util::attr_eq(attr, "idle"))
+ {
+ let args = IdleArgs::parse(item.attrs.remove(pos).tokens)?;
+
+ // If an idle function already exists, error
+ if idle.is_some() {
+ return Err(parse::Error::new(
+ span,
+ "`#[idle]` function must appear at most once",
+ ));
+ }
+
+ check_ident(&item.sig.ident)?;
+
+ idle = Some(Idle::parse(args, item)?);
+ } else if let Some(pos) = item
+ .attrs
+ .iter()
+ .position(|attr| util::attr_eq(attr, "task"))
+ {
+ if hardware_tasks.contains_key(&item.sig.ident)
+ || software_tasks.contains_key(&item.sig.ident)
+ {
+ return Err(parse::Error::new(
+ span,
+ "this task is defined multiple times",
+ ));
+ }
+
+ match syntax_parse::task_args(item.attrs.remove(pos).tokens, settings)? {
+ Either::Left(args) => {
+ check_binding(&args.binds)?;
+ check_ident(&item.sig.ident)?;
+
+ hardware_tasks.insert(
+ item.sig.ident.clone(),
+ HardwareTask::parse(args, item)?,
+ );
+ }
+
+ Either::Right(args) => {
+ check_ident(&item.sig.ident)?;
+
+ software_tasks.insert(
+ item.sig.ident.clone(),
+ SoftwareTask::parse(args, item)?,
+ );
+ }
+ }
+ } else {
+ // Forward normal functions
+ user_code.push(Item::Fn(item.clone()));
+ }
+ }
+
+ Item::Struct(ref mut struct_item) => {
+ // Match structures with the attribute #[shared], name of structure is not
+ // important
+ if let Some(_pos) = struct_item
+ .attrs
+ .iter()
+ .position(|attr| util::attr_eq(attr, "shared"))
+ {
+ let span = struct_item.ident.span();
+
+ shared_resources_ident = Some(struct_item.ident.clone());
+
+ if !shared_resources.is_empty() {
+ return Err(parse::Error::new(
+ span,
+ "`#[shared]` struct must appear at most once",
+ ));
+ }
+
+ if struct_item.vis != Visibility::Inherited {
+ return Err(parse::Error::new(
+ struct_item.span(),
+ "this item must have inherited / private visibility",
+ ));
+ }
+
+ if let Fields::Named(fields) = &mut struct_item.fields {
+ for field in &mut fields.named {
+ let ident = field.ident.as_ref().expect("UNREACHABLE");
+
+ if shared_resources.contains_key(ident) {
+ return Err(parse::Error::new(
+ ident.span(),
+ "this resource is listed more than once",
+ ));
+ }
+
+ shared_resources.insert(
+ ident.clone(),
+ SharedResource::parse(field, ident.span())?,
+ );
+ }
+ } else {
+ return Err(parse::Error::new(
+ struct_item.span(),
+ "this `struct` must have named fields",
+ ));
+ }
+ } else if let Some(_pos) = struct_item
+ .attrs
+ .iter()
+ .position(|attr| util::attr_eq(attr, "local"))
+ {
+ let span = struct_item.ident.span();
+
+ local_resources_ident = Some(struct_item.ident.clone());
+
+ if !local_resources.is_empty() {
+ return Err(parse::Error::new(
+ span,
+ "`#[local]` struct must appear at most once",
+ ));
+ }
+
+ if struct_item.vis != Visibility::Inherited {
+ return Err(parse::Error::new(
+ struct_item.span(),
+ "this item must have inherited / private visibility",
+ ));
+ }
+
+ if let Fields::Named(fields) = &mut struct_item.fields {
+ for field in &mut fields.named {
+ let ident = field.ident.as_ref().expect("UNREACHABLE");
+
+ if local_resources.contains_key(ident) {
+ return Err(parse::Error::new(
+ ident.span(),
+ "this resource is listed more than once",
+ ));
+ }
+
+ local_resources.insert(
+ ident.clone(),
+ LocalResource::parse(field, ident.span())?,
+ );
+ }
+ } else {
+ return Err(parse::Error::new(
+ struct_item.span(),
+ "this `struct` must have named fields",
+ ));
+ }
+ } else {
+ // Structure without the #[resources] attribute should just be passed along
+ user_code.push(item.clone());
+ }
+ }
+
+ Item::ForeignMod(mod_) => {
+ if !util::abi_is_rust(&mod_.abi) {
+ return Err(parse::Error::new(
+ mod_.abi.extern_token.span(),
+ "this `extern` block must use the \"Rust\" ABI",
+ ));
+ }
+
+ for item in mod_.items {
+ if let ForeignItem::Fn(mut item) = item {
+ let span = item.sig.ident.span();
+ if let Some(pos) = item
+ .attrs
+ .iter()
+ .position(|attr| util::attr_eq(attr, "task"))
+ {
+ if hardware_tasks.contains_key(&item.sig.ident)
+ || software_tasks.contains_key(&item.sig.ident)
+ {
+ return Err(parse::Error::new(
+ span,
+ "this task is defined multiple times",
+ ));
+ }
+
+ if item.attrs.len() != 1 {
+ return Err(parse::Error::new(
+ span,
+ "`extern` task required `#[task(..)]` attribute",
+ ));
+ }
+
+ match syntax_parse::task_args(
+ item.attrs.remove(pos).tokens,
+ settings,
+ )? {
+ Either::Left(args) => {
+ check_binding(&args.binds)?;
+ check_ident(&item.sig.ident)?;
+
+ hardware_tasks.insert(
+ item.sig.ident.clone(),
+ HardwareTask::parse_foreign(args, item)?,
+ );
+ }
+
+ Either::Right(args) => {
+ check_ident(&item.sig.ident)?;
+
+ software_tasks.insert(
+ item.sig.ident.clone(),
+ SoftwareTask::parse_foreign(args, item)?,
+ );
+ }
+ }
+ } else {
+ return Err(parse::Error::new(
+ span,
+ "`extern` task required `#[task(..)]` attribute",
+ ));
+ }
+ } else {
+ return Err(parse::Error::new(
+ item.span(),
+ "this item must live outside the `#[app]` module",
+ ));
+ }
+ }
+ }
+ Item::Use(itemuse_) => {
+ // Store the user provided use-statements
+ user_imports.push(itemuse_.clone());
+ }
+ Item::Type(ref mut type_item) => {
+ // Match types with the attribute #[monotonic]
+ if let Some(pos) = type_item
+ .attrs
+ .iter()
+ .position(|attr| util::attr_eq(attr, "monotonic"))
+ {
+ let span = type_item.ident.span();
+
+ if monotonics.contains_key(&type_item.ident) {
+ return Err(parse::Error::new(
+ span,
+ "`#[monotonic(...)]` on a specific type must appear at most once",
+ ));
+ }
+
+ if type_item.vis != Visibility::Inherited {
+ return Err(parse::Error::new(
+ type_item.span(),
+ "this item must have inherited / private visibility",
+ ));
+ }
+
+ check_monotonic(&*type_item.ty)?;
+
+ let m = type_item.attrs.remove(pos);
+ let args = MonotonicArgs::parse(m)?;
+
+ check_binding(&args.binds)?;
+
+ let monotonic = Monotonic::parse(args, type_item, span)?;
+
+ monotonics.insert(type_item.ident.clone(), monotonic);
+ }
+
+ // All types are passed on
+ user_code.push(item.clone());
+ }
+ _ => {
+ // Anything else within the module should not make any difference
+ user_code.push(item.clone());
+ }
+ }
+ }
+
+ let shared_resources_ident =
+ shared_resources_ident.expect("No `#[shared]` resource struct defined");
+ let local_resources_ident =
+ local_resources_ident.expect("No `#[local]` resource struct defined");
+ let init = init.expect("No `#[init]` function defined");
+
+ if shared_resources_ident != init.user_shared_struct {
+ return Err(parse::Error::new(
+ init.user_shared_struct.span(),
+ format!(
+ "This name and the one defined on `#[shared]` are not the same. Should this be `{}`?",
+ shared_resources_ident
+ ),
+ ));
+ }
+
+ if local_resources_ident != init.user_local_struct {
+ return Err(parse::Error::new(
+ init.user_local_struct.span(),
+ format!(
+ "This name and the one defined on `#[local]` are not the same. Should this be `{}`?",
+ local_resources_ident
+ ),
+ ));
+ }
+
+ Ok(App {
+ args,
+ name: input.ident,
+ init,
+ idle,
+ monotonics,
+ shared_resources,
+ local_resources,
+ user_imports,
+ user_code,
+ hardware_tasks,
+ software_tasks,
+ })
+ }
+}
diff --git a/macros/src/syntax/parse/hardware_task.rs b/macros/src/syntax/parse/hardware_task.rs
new file mode 100644
index 00000000..304bfcd3
--- /dev/null
+++ b/macros/src/syntax/parse/hardware_task.rs
@@ -0,0 +1,96 @@
+use syn::{parse, ForeignItemFn, ItemFn, Stmt};
+
+use crate::syntax::parse::util::FilterAttrs;
+use crate::syntax::{
+ ast::{HardwareTask, HardwareTaskArgs},
+ parse::util,
+};
+
+impl HardwareTask {
+ pub(crate) fn parse(args: HardwareTaskArgs, item: ItemFn) -> parse::Result<Self> {
+ let span = item.sig.ident.span();
+ let valid_signature = util::check_fn_signature(&item, false)
+ && item.sig.inputs.len() == 1
+ && util::type_is_unit(&item.sig.output);
+
+ let name = item.sig.ident.to_string();
+
+ if name == "init" || name == "idle" {
+ return Err(parse::Error::new(
+ span,
+ "tasks cannot be named `init` or `idle`",
+ ));
+ }
+
+ if valid_signature {
+ if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) {
+ if rest.is_empty() {
+ let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs);
+
+ return Ok(HardwareTask {
+ args,
+ cfgs,
+ attrs,
+ context,
+ stmts: item.block.stmts,
+ is_extern: false,
+ });
+ }
+ }
+ }
+
+ Err(parse::Error::new(
+ span,
+ &format!(
+ "this task handler must have type signature `fn({}::Context)`",
+ name
+ ),
+ ))
+ }
+}
+
+impl HardwareTask {
+ pub(crate) fn parse_foreign(
+ args: HardwareTaskArgs,
+ item: ForeignItemFn,
+ ) -> parse::Result<Self> {
+ let span = item.sig.ident.span();
+ let valid_signature = util::check_foreign_fn_signature(&item, false)
+ && item.sig.inputs.len() == 1
+ && util::type_is_unit(&item.sig.output);
+
+ let name = item.sig.ident.to_string();
+
+ if name == "init" || name == "idle" {
+ return Err(parse::Error::new(
+ span,
+ "tasks cannot be named `init` or `idle`",
+ ));
+ }
+
+ if valid_signature {
+ if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) {
+ if rest.is_empty() {
+ let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs);
+
+ return Ok(HardwareTask {
+ args,
+ cfgs,
+ attrs,
+ context,
+ stmts: Vec::<Stmt>::new(),
+ is_extern: true,
+ });
+ }
+ }
+ }
+
+ Err(parse::Error::new(
+ span,
+ &format!(
+ "this task handler must have type signature `fn({}::Context)`",
+ name
+ ),
+ ))
+ }
+}
diff --git a/macros/src/syntax/parse/idle.rs b/macros/src/syntax/parse/idle.rs
new file mode 100644
index 00000000..d9f3a99e
--- /dev/null
+++ b/macros/src/syntax/parse/idle.rs
@@ -0,0 +1,45 @@
+use proc_macro2::TokenStream as TokenStream2;
+use syn::{parse, ItemFn};
+
+use crate::syntax::{
+ ast::{Idle, IdleArgs},
+ parse::util,
+};
+
+impl IdleArgs {
+ pub(crate) fn parse(tokens: TokenStream2) -> parse::Result<Self> {
+ crate::syntax::parse::idle_args(tokens)
+ }
+}
+
+impl Idle {
+ pub(crate) fn parse(args: IdleArgs, item: ItemFn) -> parse::Result<Self> {
+ let valid_signature = util::check_fn_signature(&item, false)
+ && item.sig.inputs.len() == 1
+ && util::type_is_bottom(&item.sig.output);
+
+ let name = item.sig.ident.to_string();
+
+ if valid_signature {
+ if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) {
+ if rest.is_empty() {
+ return Ok(Idle {
+ args,
+ attrs: item.attrs,
+ context,
+ name: item.sig.ident,
+ stmts: item.block.stmts,
+ });
+ }
+ }
+ }
+
+ Err(parse::Error::new(
+ item.sig.ident.span(),
+ &format!(
+ "this `#[idle]` function must have signature `fn({}::Context) -> !`",
+ name
+ ),
+ ))
+ }
+}
diff --git a/macros/src/syntax/parse/init.rs b/macros/src/syntax/parse/init.rs
new file mode 100644
index 00000000..727ee205
--- /dev/null
+++ b/macros/src/syntax/parse/init.rs
@@ -0,0 +1,52 @@
+use proc_macro2::TokenStream as TokenStream2;
+
+use syn::{parse, ItemFn};
+
+use crate::syntax::{
+ ast::{Init, InitArgs},
+ parse::{self as syntax_parse, util},
+};
+
+impl InitArgs {
+ pub(crate) fn parse(tokens: TokenStream2) -> parse::Result<Self> {
+ syntax_parse::init_args(tokens)
+ }
+}
+
+impl Init {
+ pub(crate) fn parse(args: InitArgs, item: ItemFn) -> parse::Result<Self> {
+ let valid_signature = util::check_fn_signature(&item, false) && item.sig.inputs.len() == 1;
+
+ let span = item.sig.ident.span();
+
+ let name = item.sig.ident.to_string();
+
+ if valid_signature {
+ if let Ok((user_shared_struct, user_local_struct)) =
+ util::type_is_init_return(&item.sig.output, &name)
+ {
+ if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) {
+ if rest.is_empty() {
+ return Ok(Init {
+ args,
+ attrs: item.attrs,
+ context,
+ name: item.sig.ident,
+ stmts: item.block.stmts,
+ user_shared_struct,
+ user_local_struct,
+ });
+ }
+ }
+ }
+ }
+
+ Err(parse::Error::new(
+ span,
+ &format!(
+ "the `#[init]` function must have signature `fn({}::Context) -> (Shared resources struct, Local resources struct, {0}::Monotonics)`",
+ name
+ ),
+ ))
+ }
+}
diff --git a/macros/src/syntax/parse/monotonic.rs b/macros/src/syntax/parse/monotonic.rs
new file mode 100644
index 00000000..05832339
--- /dev/null
+++ b/macros/src/syntax/parse/monotonic.rs
@@ -0,0 +1,42 @@
+use proc_macro2::Span;
+use syn::Attribute;
+use syn::{parse, spanned::Spanned, ItemType, Visibility};
+
+use crate::syntax::parse::util::FilterAttrs;
+use crate::syntax::{
+ ast::{Monotonic, MonotonicArgs},
+ parse::util,
+};
+
+impl MonotonicArgs {
+ pub(crate) fn parse(attr: Attribute) -> parse::Result<Self> {
+ crate::syntax::parse::monotonic_args(attr.path, attr.tokens)
+ }
+}
+
+impl Monotonic {
+ pub(crate) fn parse(args: MonotonicArgs, item: &ItemType, span: Span) -> parse::Result<Self> {
+ if item.vis != Visibility::Inherited {
+ return Err(parse::Error::new(
+ span,
+ "this field must have inherited / private visibility",
+ ));
+ }
+
+ let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs.clone());
+
+ if !attrs.is_empty() {
+ return Err(parse::Error::new(
+ attrs[0].path.span(),
+ "Monotonic does not support attributes other than `#[cfg]`",
+ ));
+ }
+
+ Ok(Monotonic {
+ cfgs,
+ ident: item.ident.clone(),
+ ty: item.ty.clone(),
+ args,
+ })
+ }
+}
diff --git a/macros/src/syntax/parse/resource.rs b/macros/src/syntax/parse/resource.rs
new file mode 100644
index 00000000..ff100576
--- /dev/null
+++ b/macros/src/syntax/parse/resource.rs
@@ -0,0 +1,55 @@
+use proc_macro2::Span;
+use syn::{parse, Field, Visibility};
+
+use crate::syntax::parse::util::FilterAttrs;
+use crate::syntax::{
+ ast::{LocalResource, SharedResource, SharedResourceProperties},
+ parse::util,
+};
+
+impl SharedResource {
+ pub(crate) fn parse(item: &Field, span: Span) -> parse::Result<Self> {
+ if item.vis != Visibility::Inherited {
+ return Err(parse::Error::new(
+ span,
+ "this field must have inherited / private visibility",
+ ));
+ }
+
+ let FilterAttrs {
+ cfgs,
+ mut attrs,
+ docs,
+ } = util::filter_attributes(item.attrs.clone());
+
+ let lock_free = util::extract_lock_free(&mut attrs)?;
+
+ Ok(SharedResource {
+ cfgs,
+ attrs,
+ docs,
+ ty: Box::new(item.ty.clone()),
+ properties: SharedResourceProperties { lock_free },
+ })
+ }
+}
+
+impl LocalResource {
+ pub(crate) fn parse(item: &Field, span: Span) -> parse::Result<Self> {
+ if item.vis != Visibility::Inherited {
+ return Err(parse::Error::new(
+ span,
+ "this field must have inherited / private visibility",
+ ));
+ }
+
+ let FilterAttrs { cfgs, attrs, docs } = util::filter_attributes(item.attrs.clone());
+
+ Ok(LocalResource {
+ cfgs,
+ attrs,
+ docs,
+ ty: Box::new(item.ty.clone()),
+ })
+ }
+}
diff --git a/macros/src/syntax/parse/software_task.rs b/macros/src/syntax/parse/software_task.rs
new file mode 100644
index 00000000..2b1ac4a5
--- /dev/null
+++ b/macros/src/syntax/parse/software_task.rs
@@ -0,0 +1,86 @@
+use syn::{parse, ForeignItemFn, ItemFn, Stmt};
+
+use crate::syntax::parse::util::FilterAttrs;
+use crate::syntax::{
+ ast::{SoftwareTask, SoftwareTaskArgs},
+ parse::util,
+};
+
+impl SoftwareTask {
+ pub(crate) fn parse(args: SoftwareTaskArgs, item: ItemFn) -> parse::Result<Self> {
+ let valid_signature =
+ util::check_fn_signature(&item, true) && util::type_is_unit(&item.sig.output);
+
+ let span = item.sig.ident.span();
+
+ let name = item.sig.ident.to_string();
+
+ let is_async = item.sig.asyncness.is_some();
+
+ if valid_signature {
+ if let Some((context, Ok(inputs))) = util::parse_inputs(item.sig.inputs, &name) {
+ let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs);
+
+ return Ok(SoftwareTask {
+ args,
+ attrs,
+ cfgs,
+ context,
+ inputs,
+ stmts: item.block.stmts,
+ is_extern: false,
+ is_async,
+ });
+ }
+ }
+
+ Err(parse::Error::new(
+ span,
+ &format!(
+ "this task handler must have type signature `(async) fn({}::Context, ..)`",
+ name
+ ),
+ ))
+ }
+}
+
+impl SoftwareTask {
+ pub(crate) fn parse_foreign(
+ args: SoftwareTaskArgs,
+ item: ForeignItemFn,
+ ) -> parse::Result<Self> {
+ let valid_signature =
+ util::check_foreign_fn_signature(&item, true) && util::type_is_unit(&item.sig.output);
+
+ let span = item.sig.ident.span();
+
+ let name = item.sig.ident.to_string();
+
+ let is_async = item.sig.asyncness.is_some();
+
+ if valid_signature {
+ if let Some((context, Ok(inputs))) = util::parse_inputs(item.sig.inputs, &name) {
+ let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs);
+
+ return Ok(SoftwareTask {
+ args,
+ attrs,
+ cfgs,
+ context,
+ inputs,
+ stmts: Vec::<Stmt>::new(),
+ is_extern: true,
+ is_async,
+ });
+ }
+ }
+
+ Err(parse::Error::new(
+ span,
+ &format!(
+ "this task handler must have type signature `(async) fn({}::Context, ..)`",
+ name
+ ),
+ ))
+ }
+}
diff --git a/macros/src/syntax/parse/util.rs b/macros/src/syntax/parse/util.rs
new file mode 100644
index 00000000..3fa51ef8
--- /dev/null
+++ b/macros/src/syntax/parse/util.rs
@@ -0,0 +1,338 @@
+use syn::{
+ bracketed,
+ parse::{self, ParseStream},
+ punctuated::Punctuated,
+ spanned::Spanned,
+ Abi, AttrStyle, Attribute, Expr, FnArg, ForeignItemFn, Ident, ItemFn, Pat, PatType, Path,
+ PathArguments, ReturnType, Token, Type, Visibility,
+};
+
+use crate::syntax::{
+ ast::{Access, Local, LocalResources, SharedResources, TaskLocal},
+ Map,
+};
+
+pub fn abi_is_rust(abi: &Abi) -> bool {
+ match &abi.name {
+ None => true,
+ Some(s) => s.value() == "Rust",
+ }
+}
+
+pub fn attr_eq(attr: &Attribute, name: &str) -> bool {
+ attr.style == AttrStyle::Outer && attr.path.segments.len() == 1 && {
+ let segment = attr.path.segments.first().unwrap();
+ segment.arguments == PathArguments::None && *segment.ident.to_string() == *name
+ }
+}
+
+/// checks that a function signature
+///
+/// - has no bounds (like where clauses)
+/// - is not `async`
+/// - is not `const`
+/// - is not `unsafe`
+/// - is not generic (has no type parameters)
+/// - is not variadic
+/// - uses the Rust ABI (and not e.g. "C")
+pub fn check_fn_signature(item: &ItemFn, allow_async: bool) -> bool {
+ item.vis == Visibility::Inherited
+ && item.sig.constness.is_none()
+ && (item.sig.asyncness.is_none() || allow_async)
+ && item.sig.abi.is_none()
+ && item.sig.unsafety.is_none()
+ && item.sig.generics.params.is_empty()
+ && item.sig.generics.where_clause.is_none()
+ && item.sig.variadic.is_none()
+}
+
+#[allow(dead_code)]
+pub fn check_foreign_fn_signature(item: &ForeignItemFn, allow_async: bool) -> bool {
+ item.vis == Visibility::Inherited
+ && item.sig.constness.is_none()
+ && (item.sig.asyncness.is_none() || allow_async)
+ && item.sig.abi.is_none()
+ && item.sig.unsafety.is_none()
+ && item.sig.generics.params.is_empty()
+ && item.sig.generics.where_clause.is_none()
+ && item.sig.variadic.is_none()
+}
+
+pub struct FilterAttrs {
+ pub cfgs: Vec<Attribute>,
+ pub docs: Vec<Attribute>,
+ pub attrs: Vec<Attribute>,
+}
+
+pub fn filter_attributes(input_attrs: Vec<Attribute>) -> FilterAttrs {
+ let mut cfgs = vec![];
+ let mut docs = vec![];
+ let mut attrs = vec![];
+
+ for attr in input_attrs {
+ if attr_eq(&attr, "cfg") {
+ cfgs.push(attr);
+ } else if attr_eq(&attr, "doc") {
+ docs.push(attr);
+ } else {
+ attrs.push(attr);
+ }
+ }
+
+ FilterAttrs { cfgs, docs, attrs }
+}
+
+pub fn extract_lock_free(attrs: &mut Vec<Attribute>) -> parse::Result<bool> {
+ if let Some(pos) = attrs.iter().position(|attr| attr_eq(attr, "lock_free")) {
+ attrs.remove(pos);
+ Ok(true)
+ } else {
+ Ok(false)
+ }
+}
+
+pub fn parse_shared_resources(content: ParseStream<'_>) -> parse::Result<SharedResources> {
+ let inner;
+ bracketed!(inner in content);
+
+ let mut resources = Map::new();
+ for e in inner.call(Punctuated::<Expr, Token![,]>::parse_terminated)? {
+ let err = Err(parse::Error::new(
+ e.span(),
+ "identifier appears more than once in list",
+ ));
+ let (access, path) = match e {
+ Expr::Path(e) => (Access::Exclusive, e.path),
+
+ Expr::Reference(ref r) if r.mutability.is_none() => match &*r.expr {
+ Expr::Path(e) => (Access::Shared, e.path.clone()),
+
+ _ => return err,
+ },
+
+ _ => return err,
+ };
+
+ let ident = extract_resource_name_ident(path)?;
+
+ if resources.contains_key(&ident) {
+ return Err(parse::Error::new(
+ ident.span(),
+ "resource appears more than once in list",
+ ));
+ }
+
+ resources.insert(ident, access);
+ }
+
+ Ok(resources)
+}
+
+fn extract_resource_name_ident(path: Path) -> parse::Result<Ident> {
+ if path.leading_colon.is_some()
+ || path.segments.len() != 1
+ || path.segments[0].arguments != PathArguments::None
+ {
+ Err(parse::Error::new(
+ path.span(),
+ "resource must be an identifier, not a path",
+ ))
+ } else {
+ Ok(path.segments[0].ident.clone())
+ }
+}
+
+pub fn parse_local_resources(content: ParseStream<'_>) -> parse::Result<LocalResources> {
+ let inner;
+ bracketed!(inner in content);
+
+ let mut resources = Map::new();
+
+ for e in inner.call(Punctuated::<Expr, Token![,]>::parse_terminated)? {
+ let err = Err(parse::Error::new(
+ e.span(),
+ "identifier appears more than once in list",
+ ));
+
+ let (name, local) = match e {
+ // local = [IDENT],
+ Expr::Path(path) => {
+ if !path.attrs.is_empty() {
+ return Err(parse::Error::new(
+ path.span(),
+ "attributes are not supported here",
+ ));
+ }
+
+ let ident = extract_resource_name_ident(path.path)?;
+ // let (cfgs, attrs) = extract_cfgs(path.attrs);
+
+ (ident, TaskLocal::External)
+ }
+
+ // local = [IDENT: TYPE = EXPR]
+ Expr::Assign(e) => {
+ let (name, ty, cfgs, attrs) = match *e.left {
+ Expr::Type(t) => {
+ // Extract name and attributes
+ let (name, cfgs, attrs) = match *t.expr {
+ Expr::Path(path) => {
+ let name = extract_resource_name_ident(path.path)?;
+ let FilterAttrs { cfgs, attrs, .. } = filter_attributes(path.attrs);
+
+ (name, cfgs, attrs)
+ }
+ _ => return err,
+ };
+
+ let ty = t.ty;
+
+ // Error check
+ match &*ty {
+ Type::Array(_) => {}
+ Type::Path(_) => {}
+ Type::Ptr(_) => {}
+ Type::Tuple(_) => {}
+ _ => return Err(parse::Error::new(
+ ty.span(),
+ "unsupported type, must be an array, tuple, pointer or type path",
+ )),
+ };
+
+ (name, ty, cfgs, attrs)
+ }
+ e => return Err(parse::Error::new(e.span(), "malformed, expected a type")),
+ };
+
+ let expr = e.right; // Expr
+
+ (
+ name,
+ TaskLocal::Declared(Local {
+ attrs,
+ cfgs,
+ ty,
+ expr,
+ }),
+ )
+ }
+
+ expr => {
+ return Err(parse::Error::new(
+ expr.span(),
+ "malformed, expected 'IDENT: TYPE = EXPR'",
+ ))
+ }
+ };
+
+ resources.insert(name, local);
+ }
+
+ Ok(resources)
+}
+
+type ParseInputResult = Option<(Box<Pat>, Result<Vec<PatType>, FnArg>)>;
+
+pub fn parse_inputs(inputs: Punctuated<FnArg, Token![,]>, name: &str) -> ParseInputResult {
+ let mut inputs = inputs.into_iter();
+
+ match inputs.next() {
+ Some(FnArg::Typed(first)) => {
+ if type_is_path(&first.ty, &[name, "Context"]) {
+ let rest = inputs
+ .map(|arg| match arg {
+ FnArg::Typed(arg) => Ok(arg),
+ _ => Err(arg),
+ })
+ .collect::<Result<Vec<_>, _>>();
+
+ Some((first.pat, rest))
+ } else {
+ None
+ }
+ }
+
+ _ => None,
+ }
+}
+
+pub fn type_is_bottom(ty: &ReturnType) -> bool {
+ if let ReturnType::Type(_, ty) = ty {
+ matches!(**ty, Type::Never(_))
+ } else {
+ false
+ }
+}
+
+fn extract_init_resource_name_ident(ty: Type) -> Result<Ident, ()> {
+ match ty {
+ Type::Path(path) => {
+ let path = path.path;
+
+ if path.leading_colon.is_some()
+ || path.segments.len() != 1
+ || path.segments[0].arguments != PathArguments::None
+ {
+ Err(())
+ } else {
+ Ok(path.segments[0].ident.clone())
+ }
+ }
+ _ => Err(()),
+ }
+}
+
+/// Checks Init's return type, return the user provided types for analysis
+pub fn type_is_init_return(ty: &ReturnType, name: &str) -> Result<(Ident, Ident), ()> {
+ match ty {
+ ReturnType::Default => Err(()),
+
+ ReturnType::Type(_, ty) => match &**ty {
+ Type::Tuple(t) => {
+ // return should be:
+ // fn -> (User's #[shared] struct, User's #[local] struct, {name}::Monotonics)
+ //
+ // We check the length and the last one here, analysis checks that the user
+ // provided structs are correct.
+ if t.elems.len() == 3 && type_is_path(&t.elems[2], &[name, "Monotonics"]) {
+ return Ok((
+ extract_init_resource_name_ident(t.elems[0].clone())?,
+ extract_init_resource_name_ident(t.elems[1].clone())?,
+ ));
+ }
+
+ Err(())
+ }
+
+ _ => Err(()),
+ },
+ }
+}
+
+pub fn type_is_path(ty: &Type, segments: &[&str]) -> bool {
+ match ty {
+ Type::Path(tpath) if tpath.qself.is_none() => {
+ tpath.path.segments.len() == segments.len()
+ && tpath
+ .path
+ .segments
+ .iter()
+ .zip(segments)
+ .all(|(lhs, rhs)| lhs.ident == **rhs)
+ }
+
+ _ => false,
+ }
+}
+
+pub fn type_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
+ }
+}