aboutsummaryrefslogtreecommitdiff
path: root/rtic-macros/src
diff options
context:
space:
mode:
Diffstat (limited to 'rtic-macros/src')
-rw-r--r--rtic-macros/src/analyze.rs49
-rw-r--r--rtic-macros/src/bindings.rs1
-rw-r--r--rtic-macros/src/check.rs70
-rw-r--r--rtic-macros/src/codegen.rs75
-rw-r--r--rtic-macros/src/codegen/assertions.rs53
-rw-r--r--rtic-macros/src/codegen/async_dispatchers.rs89
-rw-r--r--rtic-macros/src/codegen/hardware_tasks.rs87
-rw-r--r--rtic-macros/src/codegen/idle.rs58
-rw-r--r--rtic-macros/src/codegen/init.rs95
-rw-r--r--rtic-macros/src/codegen/local_resources.rs65
-rw-r--r--rtic-macros/src/codegen/local_resources_struct.rs102
-rw-r--r--rtic-macros/src/codegen/main.rs52
-rw-r--r--rtic-macros/src/codegen/module.rs197
-rw-r--r--rtic-macros/src/codegen/post_init.rs47
-rw-r--r--rtic-macros/src/codegen/pre_init.rs85
-rw-r--r--rtic-macros/src/codegen/shared_resources.rs183
-rw-r--r--rtic-macros/src/codegen/shared_resources_struct.rs119
-rw-r--r--rtic-macros/src/codegen/software_tasks.rs64
-rw-r--r--rtic-macros/src/codegen/util.rs238
-rw-r--r--rtic-macros/src/lib.rs91
-rw-r--r--rtic-macros/src/syntax.rs121
-rw-r--r--rtic-macros/src/syntax/.travis.yml31
-rw-r--r--rtic-macros/src/syntax/accessors.rs113
-rw-r--r--rtic-macros/src/syntax/analyze.rs414
-rw-r--r--rtic-macros/src/syntax/ast.rs335
-rw-r--r--rtic-macros/src/syntax/check.rs66
-rw-r--r--rtic-macros/src/syntax/optimize.rs36
-rw-r--r--rtic-macros/src/syntax/parse.rs319
-rw-r--r--rtic-macros/src/syntax/parse/app.rs480
-rw-r--r--rtic-macros/src/syntax/parse/hardware_task.rs76
-rw-r--r--rtic-macros/src/syntax/parse/idle.rs42
-rw-r--r--rtic-macros/src/syntax/parse/init.rs51
-rw-r--r--rtic-macros/src/syntax/parse/resource.rs55
-rw-r--r--rtic-macros/src/syntax/parse/software_task.rs76
-rw-r--r--rtic-macros/src/syntax/parse/util.rs338
35 files changed, 4373 insertions, 0 deletions
diff --git a/rtic-macros/src/analyze.rs b/rtic-macros/src/analyze.rs
new file mode 100644
index 00000000..65774f6c
--- /dev/null
+++ b/rtic-macros/src/analyze.rs
@@ -0,0 +1,49 @@
+use core::ops;
+use std::collections::{BTreeMap, BTreeSet};
+
+use crate::syntax::{
+ analyze::{self, Priority},
+ ast::{App, Dispatcher},
+};
+use syn::Ident;
+
+/// Extend the upstream `Analysis` struct with our field
+pub struct Analysis {
+ parent: analyze::Analysis,
+ pub interrupts: BTreeMap<Priority, (Ident, Dispatcher)>,
+}
+
+impl ops::Deref for Analysis {
+ type Target = analyze::Analysis;
+
+ fn deref(&self) -> &Self::Target {
+ &self.parent
+ }
+}
+
+// Assign an interrupt to each priority level
+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()
+ .map(|task| task.args.priority)
+ .collect::<BTreeSet<_>>();
+
+ // map from priorities to interrupts (holding name and attributes)
+
+ let interrupts: BTreeMap<Priority, _> = priorities
+ .iter()
+ .filter(|prio| **prio > 0) // 0 prio tasks are run in main
+ .copied()
+ .rev()
+ .map(|p| (p, available_interrupt.pop().expect("UNREACHABLE")))
+ .collect();
+
+ Analysis {
+ parent: analysis,
+ interrupts,
+ }
+}
diff --git a/rtic-macros/src/bindings.rs b/rtic-macros/src/bindings.rs
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/rtic-macros/src/bindings.rs
@@ -0,0 +1 @@
+
diff --git a/rtic-macros/src/check.rs b/rtic-macros/src/check.rs
new file mode 100644
index 00000000..a05c82e8
--- /dev/null
+++ b/rtic-macros/src/check.rs
@@ -0,0 +1,70 @@
+use std::collections::HashSet;
+
+use crate::syntax::ast::App;
+use syn::parse;
+
+pub fn app(app: &App) -> parse::Result<()> {
+ // Check that external (device-specific) interrupts are not named after known (Cortex-M)
+ // exceptions
+ for name in app.args.dispatchers.keys() {
+ let name_s = name.to_string();
+
+ match &*name_s {
+ "NonMaskableInt" | "HardFault" | "MemoryManagement" | "BusFault" | "UsageFault"
+ | "SecureFault" | "SVCall" | "DebugMonitor" | "PendSV" | "SysTick" => {
+ return Err(parse::Error::new(
+ name.span(),
+ "Cortex-M exceptions can't be used as `extern` interrupts",
+ ));
+ }
+
+ _ => {}
+ }
+ }
+
+ // Check that there are enough external interrupts to dispatch the software tasks and the timer
+ // queue handler
+ let mut first = None;
+ let priorities = app
+ .software_tasks
+ .iter()
+ .map(|(name, task)| {
+ first = Some(name);
+ task.args.priority
+ })
+ .filter(|prio| *prio > 0)
+ .collect::<HashSet<_>>();
+
+ let need = priorities.len();
+ let given = app.args.dispatchers.len();
+ if need > given {
+ let s = {
+ format!(
+ "not enough interrupts to dispatch \
+ all software tasks (need: {need}; given: {given})"
+ )
+ };
+
+ // If not enough tasks and first still is None, may cause
+ // "custom attribute panicked" due to unwrap on None
+ return Err(parse::Error::new(first.unwrap().span(), s));
+ }
+
+ // Check that all exceptions are valid; only exceptions with configurable priorities are
+ // accepted
+ for (name, task) in &app.hardware_tasks {
+ let name_s = task.args.binds.to_string();
+ match &*name_s {
+ "NonMaskableInt" | "HardFault" => {
+ return Err(parse::Error::new(
+ name.span(),
+ "only exceptions with configurable priority can be used as hardware tasks",
+ ));
+ }
+
+ _ => {}
+ }
+ }
+
+ Ok(())
+}
diff --git a/rtic-macros/src/codegen.rs b/rtic-macros/src/codegen.rs
new file mode 100644
index 00000000..24e98ce9
--- /dev/null
+++ b/rtic-macros/src/codegen.rs
@@ -0,0 +1,75 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+
+use crate::analyze::Analysis;
+use crate::syntax::ast::App;
+
+mod assertions;
+mod async_dispatchers;
+mod hardware_tasks;
+mod idle;
+mod init;
+mod local_resources;
+mod local_resources_struct;
+mod module;
+mod post_init;
+mod pre_init;
+mod shared_resources;
+mod shared_resources_struct;
+mod software_tasks;
+mod util;
+
+mod main;
+
+// TODO: organize codegen to actual parts of code
+// so `main::codegen` generates ALL the code for `fn main`,
+// `software_tasks::codegen` generates ALL the code for software tasks etc...
+
+#[allow(clippy::too_many_lines)]
+pub fn app(app: &App, analysis: &Analysis) -> TokenStream2 {
+ // Generate the `main` function
+ let main = main::codegen(app, analysis);
+ let init_codegen = init::codegen(app, analysis);
+ let idle_codegen = idle::codegen(app, analysis);
+ let shared_resources_codegen = shared_resources::codegen(app, analysis);
+ let local_resources_codegen = local_resources::codegen(app, analysis);
+ let hardware_tasks_codegen = hardware_tasks::codegen(app, analysis);
+ let software_tasks_codegen = software_tasks::codegen(app, analysis);
+ let async_dispatchers_codegen = async_dispatchers::codegen(app, analysis);
+
+ let user_imports = &app.user_imports;
+ let user_code = &app.user_code;
+ let name = &app.name;
+ let device = &app.args.device;
+
+ let rt_err = util::rt_err_ident();
+
+ quote!(
+ /// The RTIC application module
+ pub mod #name {
+ /// Always include the device crate which contains the vector table
+ use #device as #rt_err;
+
+ #(#user_imports)*
+
+ #(#user_code)*
+ /// User code end
+
+ #init_codegen
+
+ #idle_codegen
+
+ #hardware_tasks_codegen
+
+ #software_tasks_codegen
+
+ #shared_resources_codegen
+
+ #local_resources_codegen
+
+ #async_dispatchers_codegen
+
+ #main
+ }
+ )
+}
diff --git a/rtic-macros/src/codegen/assertions.rs b/rtic-macros/src/codegen/assertions.rs
new file mode 100644
index 00000000..dd94aa6d
--- /dev/null
+++ b/rtic-macros/src/codegen/assertions.rs
@@ -0,0 +1,53 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+
+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) -> Vec<TokenStream2> {
+ let mut stmts = vec![];
+
+ for ty in &analysis.send_types {
+ stmts.push(quote!(rtic::export::assert_send::<#ty>();));
+ }
+
+ for ty in &analysis.sync_types {
+ stmts.push(quote!(rtic::export::assert_sync::<#ty>();));
+ }
+
+ let device = &app.args.device;
+ let chunks_name = util::priority_mask_chunks_ident();
+ let no_basepri_checks: Vec<_> = app
+ .hardware_tasks
+ .iter()
+ .filter_map(|(_, task)| {
+ if !util::is_exception(&task.args.binds) {
+ let interrupt_name = &task.args.binds;
+ Some(quote!(
+ 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");
+ }
+ ))
+ } else {
+ None
+ }
+ })
+ .collect();
+
+ let const_check = quote! {
+ const _CONST_CHECK: () = {
+ if !rtic::export::have_basepri() {
+ #(#no_basepri_checks)*
+ } else {
+ // TODO: Add armv7 checks here
+ }
+ };
+
+ let _ = _CONST_CHECK;
+ };
+
+ stmts.push(const_check);
+
+ stmts
+}
diff --git a/rtic-macros/src/codegen/async_dispatchers.rs b/rtic-macros/src/codegen/async_dispatchers.rs
new file mode 100644
index 00000000..a12ad325
--- /dev/null
+++ b/rtic-macros/src/codegen/async_dispatchers.rs
@@ -0,0 +1,89 @@
+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) -> TokenStream2 {
+ let mut items = vec![];
+
+ let interrupts = &analysis.interrupts;
+
+ // Generate executor definition and priority in global scope
+ for (name, _) in app.software_tasks.iter() {
+ let type_name = util::internal_task_ident(name, "F");
+ let exec_name = util::internal_task_ident(name, "EXEC");
+
+ items.push(quote!(
+ #[allow(non_camel_case_types)]
+ type #type_name = impl core::future::Future;
+ #[allow(non_upper_case_globals)]
+ static #exec_name: rtic::export::executor::AsyncTaskExecutor<#type_name> =
+ rtic::export::executor::AsyncTaskExecutor::new();
+ ));
+ }
+
+ for (&level, channel) in &analysis.channels {
+ let mut stmts = vec![];
+
+ let dispatcher_name = if level > 0 {
+ util::suffixed(&interrupts.get(&level).expect("UNREACHABLE").0.to_string())
+ } else {
+ util::zero_prio_dispatcher_ident()
+ };
+
+ let pend_interrupt = if level > 0 {
+ let device = &app.args.device;
+ let enum_ = util::interrupt_ident();
+
+ quote!(rtic::pend(#device::#enum_::#dispatcher_name);)
+ } else {
+ // For 0 priority tasks we don't need to pend anything
+ quote!()
+ };
+
+ for name in channel.tasks.iter() {
+ let exec_name = util::internal_task_ident(name, "EXEC");
+ // TODO: Fix cfg
+ // let task = &app.software_tasks[name];
+ // let cfgs = &task.cfgs;
+
+ stmts.push(quote!(
+ #exec_name.poll(|| {
+ #exec_name.set_pending();
+ #pend_interrupt
+ });
+ ));
+ }
+
+ if level > 0 {
+ let doc = format!("Interrupt handler to dispatch async tasks at priority {level}");
+ let attribute = &interrupts.get(&level).expect("UNREACHABLE").1.attrs;
+ items.push(quote!(
+ #[allow(non_snake_case)]
+ #[doc = #doc]
+ #[no_mangle]
+ #(#attribute)*
+ unsafe fn #dispatcher_name() {
+ /// The priority of this interrupt handler
+ const PRIORITY: u8 = #level;
+
+ rtic::export::run(PRIORITY, || {
+ #(#stmts)*
+ });
+ }
+ ));
+ } else {
+ items.push(quote!(
+ #[allow(non_snake_case)]
+ unsafe fn #dispatcher_name() -> ! {
+ loop {
+ #(#stmts)*
+ }
+ }
+ ));
+ }
+ }
+
+ quote!(#(#items)*)
+}
diff --git a/rtic-macros/src/codegen/hardware_tasks.rs b/rtic-macros/src/codegen/hardware_tasks.rs
new file mode 100644
index 00000000..8a5a8f6c
--- /dev/null
+++ b/rtic-macros/src/codegen/hardware_tasks.rs
@@ -0,0 +1,87 @@
+use crate::syntax::{ast::App, Context};
+use crate::{
+ analyze::Analysis,
+ 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) -> TokenStream2 {
+ let mut mod_app = vec![];
+ let mut root = vec![];
+ let mut user_tasks = vec![];
+
+ for (name, task) in &app.hardware_tasks {
+ let symbol = task.args.binds.clone();
+ let priority = task.args.priority;
+ let cfgs = &task.cfgs;
+ let attrs = &task.attrs;
+
+ mod_app.push(quote!(
+ #[allow(non_snake_case)]
+ #[no_mangle]
+ #(#attrs)*
+ #(#cfgs)*
+ unsafe fn #symbol() {
+ const PRIORITY: u8 = #priority;
+
+ rtic::export::run(PRIORITY, || {
+ #name(
+ #name::Context::new()
+ )
+ });
+ }
+ ));
+
+ // `${task}Locals`
+ if !task.args.local_resources.is_empty() {
+ let (item, constructor) =
+ local_resources_struct::codegen(Context::HardwareTask(name), app);
+
+ root.push(item);
+
+ mod_app.push(constructor);
+ }
+
+ // `${task}Resources`
+ if !task.args.shared_resources.is_empty() {
+ let (item, constructor) =
+ shared_resources_struct::codegen(Context::HardwareTask(name), app);
+
+ root.push(item);
+
+ mod_app.push(constructor);
+ }
+
+ // Module generation...
+
+ root.push(module::codegen(Context::HardwareTask(name), app, analysis));
+
+ // End module generation
+
+ if !task.is_extern {
+ let attrs = &task.attrs;
+ let context = &task.context;
+ let stmts = &task.stmts;
+ user_tasks.push(quote!(
+ #(#attrs)*
+ #[allow(non_snake_case)]
+ fn #name(#context: #name::Context) {
+ use rtic::Mutex as _;
+ use rtic::mutex::prelude::*;
+
+ #(#stmts)*
+ }
+ ));
+ }
+ }
+
+ quote!(
+ #(#mod_app)*
+
+ #(#root)*
+
+ #(#user_tasks)*
+ )
+}
diff --git a/rtic-macros/src/codegen/idle.rs b/rtic-macros/src/codegen/idle.rs
new file mode 100644
index 00000000..0c833ef3
--- /dev/null
+++ b/rtic-macros/src/codegen/idle.rs
@@ -0,0 +1,58 @@
+use crate::syntax::{ast::App, Context};
+use crate::{
+ analyze::Analysis,
+ 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) -> TokenStream2 {
+ if let Some(idle) = &app.idle {
+ let mut mod_app = vec![];
+ let mut root_idle = vec![];
+
+ let name = &idle.name;
+
+ if !idle.args.shared_resources.is_empty() {
+ let (item, constructor) = shared_resources_struct::codegen(Context::Idle, app);
+
+ root_idle.push(item);
+ mod_app.push(constructor);
+ }
+
+ if !idle.args.local_resources.is_empty() {
+ let (item, constructor) = local_resources_struct::codegen(Context::Idle, app);
+
+ root_idle.push(item);
+
+ mod_app.push(constructor);
+ }
+
+ root_idle.push(module::codegen(Context::Idle, app, analysis));
+
+ let attrs = &idle.attrs;
+ let context = &idle.context;
+ let stmts = &idle.stmts;
+ let user_idle = Some(quote!(
+ #(#attrs)*
+ #[allow(non_snake_case)]
+ fn #name(#context: #name::Context) -> ! {
+ use rtic::Mutex as _;
+ use rtic::mutex::prelude::*;
+
+ #(#stmts)*
+ }
+ ));
+
+ quote!(
+ #(#mod_app)*
+
+ #(#root_idle)*
+
+ #user_idle
+ )
+ } else {
+ quote!()
+ }
+}
diff --git a/rtic-macros/src/codegen/init.rs b/rtic-macros/src/codegen/init.rs
new file mode 100644
index 00000000..6e1059f7
--- /dev/null
+++ b/rtic-macros/src/codegen/init.rs
@@ -0,0 +1,95 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+
+use crate::{
+ analyze::Analysis,
+ codegen::{local_resources_struct, module},
+ syntax::{ast::App, Context},
+};
+
+/// Generates support code for `#[init]` functions
+pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 {
+ let init = &app.init;
+ let name = &init.name;
+
+ let mut root_init = vec![];
+
+ let context = &init.context;
+ let attrs = &init.attrs;
+ let stmts = &init.stmts;
+ let shared = &init.user_shared_struct;
+ let local = &init.user_local_struct;
+
+ let shared_resources: Vec<_> = app
+ .shared_resources
+ .iter()
+ .map(|(k, v)| {
+ let ty = &v.ty;
+ let cfgs = &v.cfgs;
+ let docs = &v.docs;
+ quote!(
+ #(#cfgs)*
+ #(#docs)*
+ #k: #ty,
+ )
+ })
+ .collect();
+ let local_resources: Vec<_> = app
+ .local_resources
+ .iter()
+ .map(|(k, v)| {
+ let ty = &v.ty;
+ let cfgs = &v.cfgs;
+ let docs = &v.docs;
+ quote!(
+ #(#cfgs)*
+ #(#docs)*
+ #k: #ty,
+ )
+ })
+ .collect();
+
+ root_init.push(quote! {
+ struct #shared {
+ #(#shared_resources)*
+ }
+
+ struct #local {
+ #(#local_resources)*
+ }
+ });
+
+ // let locals_pat = locals_pat.iter();
+
+ let user_init_return = quote! {#shared, #local};
+
+ let user_init = quote!(
+ #(#attrs)*
+ #[inline(always)]
+ #[allow(non_snake_case)]
+ fn #name(#context: #name::Context) -> (#user_init_return) {
+ #(#stmts)*
+ }
+ );
+
+ let mut mod_app = None;
+
+ // `${task}Locals`
+ if !init.args.local_resources.is_empty() {
+ let (item, constructor) = local_resources_struct::codegen(Context::Init, app);
+
+ root_init.push(item);
+
+ mod_app = Some(constructor);
+ }
+
+ root_init.push(module::codegen(Context::Init, app, analysis));
+
+ quote!(
+ #mod_app
+
+ #(#root_init)*
+
+ #user_init
+ )
+}
diff --git a/rtic-macros/src/codegen/local_resources.rs b/rtic-macros/src/codegen/local_resources.rs
new file mode 100644
index 00000000..e6d15533
--- /dev/null
+++ b/rtic-macros/src/codegen/local_resources.rs
@@ -0,0 +1,65 @@
+use crate::syntax::ast::App;
+use crate::{analyze::Analysis, codegen::util};
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+
+/// Generates `local` variables and local resource proxies
+///
+/// I.e. the `static` variables and theirs proxies.
+pub fn codegen(app: &App, _analysis: &Analysis) -> TokenStream2 {
+ let mut mod_app = vec![];
+
+ // All local resources declared in the `#[local]' struct
+ for (name, res) in &app.local_resources {
+ let cfgs = &res.cfgs;
+ let ty = &res.ty;
+ let mangled_name = util::static_local_resource_ident(name);
+
+ let attrs = &res.attrs;
+
+ // late resources in `util::link_section_uninit`
+ // unless user specifies custom link section
+ let section = if attrs.iter().any(|attr| attr.path.is_ident("link_section")) {
+ None
+ } else {
+ Some(util::link_section_uninit())
+ };
+
+ // For future use
+ // let doc = format!(" RTIC internal: {}:{}", file!(), line!());
+ mod_app.push(quote!(
+ #[allow(non_camel_case_types)]
+ #[allow(non_upper_case_globals)]
+ // #[doc = #doc]
+ #[doc(hidden)]
+ #(#attrs)*
+ #(#cfgs)*
+ #section
+ static #mangled_name: rtic::RacyCell<core::mem::MaybeUninit<#ty>> = rtic::RacyCell::new(core::mem::MaybeUninit::uninit());
+ ));
+ }
+
+ // All declared `local = [NAME: TY = EXPR]` local resources
+ for (task_name, resource_name, task_local) in app.declared_local_resources() {
+ let cfgs = &task_local.cfgs;
+ let ty = &task_local.ty;
+ let expr = &task_local.expr;
+ let attrs = &task_local.attrs;
+
+ let mangled_name = util::declared_static_local_resource_ident(resource_name, task_name);
+
+ // For future use
+ // let doc = format!(" RTIC internal: {}:{}", file!(), line!());
+ mod_app.push(quote!(
+ #[allow(non_camel_case_types)]
+ #[allow(non_upper_case_globals)]
+ // #[doc = #doc]
+ #[doc(hidden)]
+ #(#attrs)*
+ #(#cfgs)*
+ static #mangled_name: rtic::RacyCell<#ty> = rtic::RacyCell::new(#expr);
+ ));
+ }
+
+ quote!(#(#mod_app)*)
+}
diff --git a/rtic-macros/src/codegen/local_resources_struct.rs b/rtic-macros/src/codegen/local_resources_struct.rs
new file mode 100644
index 00000000..100c3eb5
--- /dev/null
+++ b/rtic-macros/src/codegen/local_resources_struct.rs
@@ -0,0 +1,102 @@
+use crate::syntax::{
+ ast::{App, TaskLocal},
+ Context,
+};
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+
+use crate::codegen::util;
+
+/// Generates local resources structs
+pub fn codegen(ctxt: Context, app: &App) -> (TokenStream2, TokenStream2) {
+ let resources = match ctxt {
+ Context::Init => &app.init.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,
+ };
+
+ let task_name = util::get_task_name(ctxt, app);
+
+ let mut fields = vec![];
+ let mut values = vec![];
+
+ for (name, task_local) in resources {
+ let (cfgs, ty, is_declared) = match task_local {
+ TaskLocal::External => {
+ let r = app.local_resources.get(name).expect("UNREACHABLE");
+ (&r.cfgs, &r.ty, false)
+ }
+ TaskLocal::Declared(r) => (&r.cfgs, &r.ty, true),
+ };
+
+ let lt = if ctxt.runs_once() {
+ quote!('static)
+ } else {
+ quote!('a)
+ };
+
+ let mangled_name = if matches!(task_local, TaskLocal::External) {
+ util::static_local_resource_ident(name)
+ } else {
+ util::declared_static_local_resource_ident(name, &task_name)
+ };
+
+ fields.push(quote!(
+ #(#cfgs)*
+ #[allow(missing_docs)]
+ pub #name: &#lt mut #ty
+ ));
+
+ let expr = if is_declared {
+ // If the local resources is already initialized, we only need to access its value and
+ // not go through an `MaybeUninit`
+ quote!(&mut *#mangled_name.get_mut())
+ } else {
+ quote!(&mut *(&mut *#mangled_name.get_mut()).as_mut_ptr())
+ };
+
+ values.push(quote!(
+ #(#cfgs)*
+ #name: #expr
+ ));
+ }
+
+ fields.push(quote!(
+ #[doc(hidden)]
+ pub __rtic_internal_marker: ::core::marker::PhantomData<&'a ()>
+ ));
+
+ values.push(quote!(__rtic_internal_marker: ::core::marker::PhantomData));
+
+ 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)]
+ #[allow(non_camel_case_types)]
+ #[doc = #doc]
+ pub struct #ident<'a> {
+ #(#fields,)*
+ }
+ );
+
+ let constructor = quote!(
+ impl<'a> #ident<'a> {
+ #[inline(always)]
+ #[allow(missing_docs)]
+ pub unsafe fn new() -> Self {
+ #ident {
+ #(#values,)*
+ }
+ }
+ }
+ );
+
+ (item, constructor)
+}
diff --git a/rtic-macros/src/codegen/main.rs b/rtic-macros/src/codegen/main.rs
new file mode 100644
index 00000000..2775d259
--- /dev/null
+++ b/rtic-macros/src/codegen/main.rs
@@ -0,0 +1,52 @@
+use crate::{analyze::Analysis, codegen::util, syntax::ast::App};
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+
+use super::{assertions, post_init, pre_init};
+
+/// Generates code for `fn main`
+pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 {
+ let assertion_stmts = assertions::codegen(app, analysis);
+
+ let pre_init_stmts = pre_init::codegen(app, analysis);
+
+ let post_init_stmts = post_init::codegen(app, analysis);
+
+ let call_idle = if let Some(idle) = &app.idle {
+ let name = &idle.name;
+ quote!(#name(#name::Context::new()))
+ } else if analysis.channels.get(&0).is_some() {
+ let dispatcher = util::zero_prio_dispatcher_ident();
+ quote!(#dispatcher();)
+ } else {
+ quote!(loop {
+ rtic::export::nop()
+ })
+ };
+
+ let main = util::suffixed("main");
+ let init_name = &app.init.name;
+ quote!(
+ #[doc(hidden)]
+ #[no_mangle]
+ unsafe extern "C" fn #main() -> ! {
+ #(#assertion_stmts)*
+
+ #(#pre_init_stmts)*
+
+ #[inline(never)]
+ fn __rtic_init_resources<F>(f: F) where F: FnOnce() {
+ f();
+ }
+
+ // Wrap late_init_stmts in a function to ensure that stack space is reclaimed.
+ __rtic_init_resources(||{
+ let (shared_resources, local_resources) = #init_name(#init_name::Context::new(core.into()));
+
+ #(#post_init_stmts)*
+ });
+
+ #call_idle
+ }
+ )
+}
diff --git a/rtic-macros/src/codegen/module.rs b/rtic-macros/src/codegen/module.rs
new file mode 100644
index 00000000..8b3fca23
--- /dev/null
+++ b/rtic-macros/src/codegen/module.rs
@@ -0,0 +1,197 @@
+use crate::syntax::{ast::App, Context};
+use crate::{analyze::Analysis, codegen::util};
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+
+#[allow(clippy::too_many_lines)]
+pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> 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);
+
+ match ctxt {
+ Context::Init => {
+ fields.push(quote!(
+ /// Core (Cortex-M) peripherals
+ pub core: rtic::export::Peripherals
+ ));
+
+ if app.args.peripherals {
+ let device = &app.args.device;
+
+ fields.push(quote!(
+ /// Device peripherals
+ pub device: #device::Peripherals
+ ));
+
+ values.push(quote!(device: #device::Peripherals::steal()));
+ }
+
+ fields.push(quote!(
+ /// Critical section token for init
+ pub cs: rtic::export::CriticalSection<'a>
+ ));
+
+ values.push(quote!(cs: rtic::export::CriticalSection::new()));
+
+ values.push(quote!(core));
+ }
+
+ Context::Idle | Context::HardwareTask(_) | Context::SoftwareTask(_) => {}
+ }
+
+ if ctxt.has_local_resources(app) {
+ let ident = util::local_resources_ident(ctxt, app);
+
+ module_items.push(quote!(
+ #[doc(inline)]
+ pub use super::#ident as LocalResources;
+ ));
+
+ fields.push(quote!(
+ /// Local Resources this task has access to
+ pub local: #name::LocalResources<'a>
+ ));
+
+ values.push(quote!(local: #name::LocalResources::new()));
+ }
+
+ if ctxt.has_shared_resources(app) {
+ let ident = util::shared_resources_ident(ctxt, app);
+
+ module_items.push(quote!(
+ #[doc(inline)]
+ pub use super::#ident as SharedResources;
+ ));
+
+ fields.push(quote!(
+ /// Shared Resources this task has access to
+ pub shared: #name::SharedResources<'a>
+ ));
+
+ values.push(quote!(shared: #name::SharedResources::new()));
+ }
+
+ let doc = match ctxt {
+ Context::Idle => "Idle loop",
+ Context::Init => "Initialization function",
+ Context::HardwareTask(_) => "Hardware task",
+ Context::SoftwareTask(_) => "Software task",
+ };
+
+ let v = Vec::new();
+ let cfgs = match ctxt {
+ Context::HardwareTask(t) => &app.hardware_tasks[t].cfgs,
+ Context::SoftwareTask(t) => &app.software_tasks[t].cfgs,
+ _ => &v,
+ };
+
+ let core = if ctxt.is_init() {
+ Some(quote!(core: rtic::export::Peripherals,))
+ } else {
+ None
+ };
+
+ let internal_context_name = util::internal_task_ident(name, "Context");
+ let exec_name = util::internal_task_ident(name, "EXEC");
+
+ items.push(quote!(
+ #(#cfgs)*
+ /// Execution context
+ #[allow(non_snake_case)]
+ #[allow(non_camel_case_types)]
+ pub struct #internal_context_name<'a> {
+ #[doc(hidden)]
+ __rtic_internal_p: ::core::marker::PhantomData<&'a ()>,
+ #(#fields,)*
+ }
+
+ #(#cfgs)*
+ impl<'a> #internal_context_name<'a> {
+ #[inline(always)]
+ #[allow(missing_docs)]
+ pub unsafe fn new(#core) -> Self {
+ #internal_context_name {
+ __rtic_internal_p: ::core::marker::PhantomData,
+ #(#values,)*
+ }
+ }
+ }
+ ));
+
+ module_items.push(quote!(
+ #(#cfgs)*
+ #[doc(inline)]
+ pub use super::#internal_context_name as Context;
+ ));
+
+ if let Context::SoftwareTask(..) = ctxt {
+ let spawnee = &app.software_tasks[name];
+ let priority = spawnee.args.priority;
+ let cfgs = &spawnee.cfgs;
+ // Store a copy of the task cfgs
+ task_cfgs = cfgs.clone();
+
+ let pend_interrupt = if priority > 0 {
+ let device = &app.args.device;
+ let enum_ = util::interrupt_ident();
+ let interrupt = &analysis.interrupts.get(&priority).expect("UREACHABLE").0;
+ quote!(rtic::pend(#device::#enum_::#interrupt);)
+ } else {
+ quote!()
+ };
+
+ let internal_spawn_ident = util::internal_task_ident(name, "spawn");
+ let (input_args, input_tupled, input_untupled, input_ty) =
+ util::regroup_inputs(&spawnee.inputs);
+
+ // Spawn caller
+ items.push(quote!(
+ #(#cfgs)*
+ /// Spawns the task directly
+ #[allow(non_snake_case)]
+ #[doc(hidden)]
+ pub fn #internal_spawn_ident(#(#input_args,)*) -> Result<(), #input_ty> {
+ // SAFETY: If `try_allocate` suceeds one must call `spawn`, which we do.
+ unsafe {
+ if #exec_name.try_allocate() {
+ let f = #name(unsafe { #name::Context::new() } #(,#input_untupled)*);
+ #exec_name.spawn(f);
+ #pend_interrupt
+
+ Ok(())
+ } else {
+ Err(#input_tupled)
+ }
+ }
+
+ }
+ ));
+
+ module_items.push(quote!(
+ #(#cfgs)*
+ #[doc(inline)]
+ pub use super::#internal_spawn_ident as spawn;
+ ));
+ }
+
+ if items.is_empty() {
+ quote!()
+ } else {
+ quote!(
+ #(#items)*
+
+ #[allow(non_snake_case)]
+ #(#task_cfgs)*
+ #[doc = #doc]
+ pub mod #name {
+ #(#module_items)*
+ }
+ )
+ }
+}
diff --git a/rtic-macros/src/codegen/post_init.rs b/rtic-macros/src/codegen/post_init.rs
new file mode 100644
index 00000000..c4e53837
--- /dev/null
+++ b/rtic-macros/src/codegen/post_init.rs
@@ -0,0 +1,47 @@
+use crate::{analyze::Analysis, codegen::util, syntax::ast::App};
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+
+/// Generates code that runs after `#[init]` returns
+pub fn codegen(app: &App, analysis: &Analysis) -> Vec<TokenStream2> {
+ let mut stmts = vec![];
+
+ // Initialize shared resources
+ for (name, res) in &app.shared_resources {
+ let mangled_name = util::static_shared_resource_ident(name);
+ // If it's live
+ let cfgs = res.cfgs.clone();
+ if analysis.shared_resources.get(name).is_some() {
+ stmts.push(quote!(
+ // We include the cfgs
+ #(#cfgs)*
+ // Resource is a RacyCell<MaybeUninit<T>>
+ // - `get_mut` to obtain a raw pointer to `MaybeUninit<T>`
+ // - `write` the defined value for the late resource T
+ #mangled_name.get_mut().write(core::mem::MaybeUninit::new(shared_resources.#name));
+ ));
+ }
+ }
+
+ // Initialize local resources
+ for (name, res) in &app.local_resources {
+ let mangled_name = util::static_local_resource_ident(name);
+ // If it's live
+ let cfgs = res.cfgs.clone();
+ if analysis.local_resources.get(name).is_some() {
+ stmts.push(quote!(
+ // We include the cfgs
+ #(#cfgs)*
+ // Resource is a RacyCell<MaybeUninit<T>>
+ // - `get_mut` to obtain a raw pointer to `MaybeUninit<T>`
+ // - `write` the defined value for the late resource T
+ #mangled_name.get_mut().write(core::mem::MaybeUninit::new(local_resources.#name));
+ ));
+ }
+ }
+
+ // Enable the interrupts -- this completes the `init`-ialization phase
+ stmts.push(quote!(rtic::export::interrupt::enable();));
+
+ stmts
+}
diff --git a/rtic-macros/src/codegen/pre_init.rs b/rtic-macros/src/codegen/pre_init.rs
new file mode 100644
index 00000000..28ba29c0
--- /dev/null
+++ b/rtic-macros/src/codegen/pre_init.rs
@@ -0,0 +1,85 @@
+use crate::syntax::ast::App;
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+
+use crate::{analyze::Analysis, codegen::util};
+
+/// Generates code that runs before `#[init]`
+pub fn codegen(app: &App, analysis: &Analysis) -> Vec<TokenStream2> {
+ let mut stmts = vec![];
+
+ let rt_err = util::rt_err_ident();
+
+ // Disable interrupts -- `init` must run with interrupts disabled
+ stmts.push(quote!(rtic::export::interrupt::disable();));
+
+ stmts.push(quote!(
+ // To set the variable in cortex_m so the peripherals cannot be taken multiple times
+ let mut core: rtic::export::Peripherals = rtic::export::Peripherals::steal().into();
+ ));
+
+ let device = &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.dispatchers.keys() {
+ stmts.push(quote!(let _ = #rt_err::#interrupt::#name;));
+ }
+
+ let interrupt_ids = analysis.interrupts.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| {
+ if util::is_exception(&task.args.binds) {
+ // We do exceptions in another pass
+ None
+ } else {
+ Some((&task.args.priority, &task.args.binds))
+ }
+ })) {
+ let es = format!(
+ "Maximum priority used by interrupt vector '{name}' is more than supported by hardware"
+ );
+ // Compile time assert that this priority is supported by the device
+ stmts.push(quote!(
+ const _: () = if (1 << #nvic_prio_bits) < #priority as usize { ::core::panic!(#es); };
+ ));
+
+ stmts.push(quote!(
+ core.NVIC.set_priority(
+ #rt_err::#interrupt::#name,
+ rtic::export::logical2hw(#priority, #nvic_prio_bits),
+ );
+ ));
+
+ // NOTE unmask the interrupt *after* setting its priority: changing the priority of a pended
+ // interrupt is implementation defined
+ stmts.push(quote!(rtic::export::NVIC::unmask(#rt_err::#interrupt::#name);));
+ }
+
+ // Set exception priorities
+ for (name, priority) in app.hardware_tasks.values().filter_map(|task| {
+ if util::is_exception(&task.args.binds) {
+ Some((&task.args.binds, task.args.priority))
+ } else {
+ None
+ }
+ }) {
+ let es = format!(
+ "Maximum priority used by interrupt vector '{name}' is more than supported by hardware"
+ );
+ // Compile time assert that this priority is supported by the device
+ stmts.push(quote!(
+ const _: () = if (1 << #nvic_prio_bits) < #priority as usize { ::core::panic!(#es); };
+ ));
+
+ stmts.push(quote!(core.SCB.set_priority(
+ rtic::export::SystemHandler::#name,
+ rtic::export::logical2hw(#priority, #nvic_prio_bits),
+ );));
+ }
+
+ stmts
+}
diff --git a/rtic-macros/src/codegen/shared_resources.rs b/rtic-macros/src/codegen/shared_resources.rs
new file mode 100644
index 00000000..19fd13fe
--- /dev/null
+++ b/rtic-macros/src/codegen/shared_resources.rs
@@ -0,0 +1,183 @@
+use crate::syntax::{analyze::Ownership, ast::App};
+use crate::{analyze::Analysis, codegen::util};
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use std::collections::HashMap;
+
+/// Generates `static` variables and shared resource proxies
+pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 {
+ let mut mod_app = vec![];
+ let mut mod_resources = vec![];
+
+ for (name, res) in &app.shared_resources {
+ let cfgs = &res.cfgs;
+ let ty = &res.ty;
+ let mangled_name = &util::static_shared_resource_ident(name);
+
+ let attrs = &res.attrs;
+
+ // late resources in `util::link_section_uninit`
+ // unless user specifies custom link section
+ let section = if attrs.iter().any(|attr| attr.path.is_ident("link_section")) {
+ None
+ } else {
+ Some(util::link_section_uninit())
+ };
+
+ // For future use
+ // let doc = format!(" RTIC internal: {}:{}", file!(), line!());
+ mod_app.push(quote!(
+ #[allow(non_camel_case_types)]
+ #[allow(non_upper_case_globals)]
+ // #[doc = #doc]
+ #[doc(hidden)]
+ #(#attrs)*
+ #(#cfgs)*
+ #section
+ static #mangled_name: rtic::RacyCell<core::mem::MaybeUninit<#ty>> = rtic::RacyCell::new(core::mem::MaybeUninit::uninit());
+ ));
+
+ // For future use
+ // let doc = format!(" RTIC internal: {}:{}", file!(), line!());
+
+ let shared_name = util::need_to_lock_ident(name);
+
+ if !res.properties.lock_free {
+ mod_resources.push(quote!(
+ // #[doc = #doc]
+ #[doc(hidden)]
+ #[allow(non_camel_case_types)]
+ #(#cfgs)*
+ pub struct #shared_name<'a> {
+ __rtic_internal_p: ::core::marker::PhantomData<&'a ()>,
+ }
+
+ #(#cfgs)*
+ impl<'a> #shared_name<'a> {
+ #[inline(always)]
+ pub unsafe fn new() -> Self {
+ #shared_name { __rtic_internal_p: ::core::marker::PhantomData }
+ }
+ }
+ ));
+
+ let ptr = quote!(
+ #(#cfgs)*
+ #mangled_name.get_mut() as *mut _
+ );
+
+ let ceiling = match analysis.ownerships.get(name) {
+ Some(Ownership::Owned { priority } | Ownership::CoOwned { priority }) => *priority,
+ Some(Ownership::Contended { ceiling }) => *ceiling,
+ None => 0,
+ };
+
+ // For future use
+ // let doc = format!(" RTIC internal ({} resource): {}:{}", doc, file!(), line!());
+
+ mod_app.push(util::impl_mutex(
+ app,
+ cfgs,
+ true,
+ &shared_name,
+ &quote!(#ty),
+ ceiling,
+ &ptr,
+ ));
+ }
+ }
+
+ let mod_resources = if mod_resources.is_empty() {
+ quote!()
+ } else {
+ quote!(mod shared_resources {
+ #(#mod_resources)*
+ })
+ };
+
+ // Computing mapping of used interrupts to masks
+ let interrupt_ids = analysis.interrupts.iter().map(|(p, (id, _))| (p, id));
+
+ let mut prio_to_masks = HashMap::new();
+ let device = &app.args.device;
+ let mut uses_exceptions_with_resources = false;
+
+ let mut mask_ids = Vec::new();
+
+ for (&priority, name) in interrupt_ids.chain(app.hardware_tasks.values().flat_map(|task| {
+ if !util::is_exception(&task.args.binds) {
+ Some((&task.args.priority, &task.args.binds))
+ } else {
+ // If any resource to the exception uses non-lock-free or non-local resources this is
+ // not allwed on thumbv6.
+ uses_exceptions_with_resources = uses_exceptions_with_resources
+ || task
+ .args
+ .shared_resources
+ .iter()
+ .map(|(ident, access)| {
+ if access.is_exclusive() {
+ if let Some(r) = app.shared_resources.get(ident) {
+ !r.properties.lock_free
+ } else {
+ false
+ }
+ } else {
+ false
+ }
+ })
+ .any(|v| v);
+
+ None
+ }
+ })) {
+ 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));
+ }
+
+ // Call rtic::export::create_mask([Mask; N]), where the array is the list of shifts
+
+ let mut mask_arr = Vec::new();
+ // NOTE: 0..3 assumes max 4 priority levels according to M0, M23 spec
+ for i in 0..3 {
+ let v = if let Some(v) = prio_to_masks.get(&i) {
+ v.clone()
+ } else {
+ Vec::new()
+ };
+
+ mask_arr.push(quote!(
+ rtic::export::create_mask([#(#v),*])
+ ));
+ }
+
+ // Generate a constant for the number of chunks needed by Mask.
+ let chunks_name = util::priority_mask_chunks_ident();
+ mod_app.push(quote!(
+ #[doc(hidden)]
+ #[allow(non_upper_case_globals)]
+ const #chunks_name: usize = rtic::export::compute_mask_chunks([#(#mask_ids),*]);
+ ));
+
+ let masks_name = util::priority_masks_ident();
+ mod_app.push(quote!(
+ #[doc(hidden)]
+ #[allow(non_upper_case_globals)]
+ const #masks_name: [rtic::export::Mask<#chunks_name>; 3] = [#(#mask_arr),*];
+ ));
+
+ if uses_exceptions_with_resources {
+ mod_app.push(quote!(
+ #[doc(hidden)]
+ #[allow(non_upper_case_globals)]
+ const __rtic_internal_V6_ERROR: () = rtic::export::no_basepri_panic();
+ ));
+ }
+
+ quote!(
+ #(#mod_app)*
+
+ #mod_resources
+ )
+}
diff --git a/rtic-macros/src/codegen/shared_resources_struct.rs b/rtic-macros/src/codegen/shared_resources_struct.rs
new file mode 100644
index 00000000..fa6f0fcb
--- /dev/null
+++ b/rtic-macros/src/codegen/shared_resources_struct.rs
@@ -0,0 +1,119 @@
+use crate::syntax::{ast::App, Context};
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+
+use crate::codegen::util;
+
+/// Generate shared resources structs
+pub fn codegen(ctxt: Context, app: &App) -> (TokenStream2, TokenStream2) {
+ let resources = match ctxt {
+ Context::Init => unreachable!("Tried to generate shared resources struct for init"),
+ 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 mut fields = vec![];
+ let mut values = vec![];
+
+ for (name, access) in resources {
+ let res = app.shared_resources.get(name).expect("UNREACHABLE");
+
+ let cfgs = &res.cfgs;
+
+ // access hold if the resource is [x] (exclusive) or [&x] (shared)
+ let mut_ = if access.is_exclusive() {
+ Some(quote!(mut))
+ } else {
+ None
+ };
+ let ty = &res.ty;
+ let mangled_name = util::static_shared_resource_ident(name);
+ let shared_name = util::need_to_lock_ident(name);
+
+ if res.properties.lock_free {
+ // Lock free resources of `idle` and `init` get 'static lifetime
+ let lt = if ctxt.runs_once() {
+ quote!('static)
+ } else {
+ quote!('a)
+ };
+
+ fields.push(quote!(
+ #(#cfgs)*
+ #[allow(missing_docs)]
+ pub #name: &#lt #mut_ #ty
+ ));
+ } else if access.is_shared() {
+ fields.push(quote!(
+ #(#cfgs)*
+ #[allow(missing_docs)]
+ pub #name: &'a #ty
+ ));
+ } else {
+ fields.push(quote!(
+ #(#cfgs)*
+ #[allow(missing_docs)]
+ pub #name: shared_resources::#shared_name<'a>
+ ));
+
+ values.push(quote!(
+ #(#cfgs)*
+ #name: shared_resources::#shared_name::new()
+
+ ));
+
+ // continue as the value has been filled,
+ continue;
+ }
+
+ let expr = if access.is_exclusive() {
+ quote!(&mut *(&mut *#mangled_name.get_mut()).as_mut_ptr())
+ } else {
+ quote!(&*(&*#mangled_name.get()).as_ptr())
+ };
+
+ values.push(quote!(
+ #(#cfgs)*
+ #name: #expr
+ ));
+ }
+
+ fields.push(quote!(
+ #[doc(hidden)]
+ pub __rtic_internal_marker: core::marker::PhantomData<&'a ()>
+ ));
+
+ values.push(quote!(__rtic_internal_marker: core::marker::PhantomData));
+
+ 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]
+ pub struct #ident<'a> {
+ #(#fields,)*
+ }
+ );
+
+ let constructor = quote!(
+ impl<'a> #ident<'a> {
+ #[inline(always)]
+ #[allow(missing_docs)]
+ pub unsafe fn new() -> Self {
+ #ident {
+ #(#values,)*
+ }
+ }
+ }
+ );
+
+ (item, constructor)
+}
diff --git a/rtic-macros/src/codegen/software_tasks.rs b/rtic-macros/src/codegen/software_tasks.rs
new file mode 100644
index 00000000..34fc851a
--- /dev/null
+++ b/rtic-macros/src/codegen/software_tasks.rs
@@ -0,0 +1,64 @@
+use crate::syntax::{ast::App, Context};
+use crate::{
+ analyze::Analysis,
+ codegen::{local_resources_struct, module, shared_resources_struct},
+};
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+
+pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 {
+ let mut mod_app = vec![];
+ let mut root = vec![];
+ let mut user_tasks = vec![];
+
+ // Any task
+ for (name, task) in app.software_tasks.iter() {
+ if !task.args.local_resources.is_empty() {
+ let (item, constructor) =
+ local_resources_struct::codegen(Context::SoftwareTask(name), app);
+
+ root.push(item);
+
+ mod_app.push(constructor);
+ }
+
+ if !task.args.shared_resources.is_empty() {
+ let (item, constructor) =
+ shared_resources_struct::codegen(Context::SoftwareTask(name), app);
+
+ root.push(item);
+
+ mod_app.push(constructor);
+ }
+
+ if !&task.is_extern {
+ let context = &task.context;
+ let attrs = &task.attrs;
+ let cfgs = &task.cfgs;
+ let stmts = &task.stmts;
+ let inputs = &task.inputs;
+
+ user_tasks.push(quote!(
+ #(#attrs)*
+ #(#cfgs)*
+ #[allow(non_snake_case)]
+ async fn #name<'a>(#context: #name::Context<'a> #(,#inputs)*) {
+ use rtic::Mutex as _;
+ use rtic::mutex::prelude::*;
+
+ #(#stmts)*
+ }
+ ));
+ }
+
+ root.push(module::codegen(Context::SoftwareTask(name), app, analysis));
+ }
+
+ quote!(
+ #(#mod_app)*
+
+ #(#root)*
+
+ #(#user_tasks)*
+ )
+}
diff --git a/rtic-macros/src/codegen/util.rs b/rtic-macros/src/codegen/util.rs
new file mode 100644
index 00000000..d0c8cc0e
--- /dev/null
+++ b/rtic-macros/src/codegen/util.rs
@@ -0,0 +1,238 @@
+use crate::syntax::{ast::App, Context};
+use core::sync::atomic::{AtomicUsize, Ordering};
+use proc_macro2::{Span, TokenStream as TokenStream2};
+use quote::quote;
+use syn::{Attribute, Ident, PatType};
+
+const RTIC_INTERNAL: &str = "__rtic_internal";
+
+/// Generates a `Mutex` implementation
+pub fn impl_mutex(
+ app: &App,
+ cfgs: &[Attribute],
+ resources_prefix: bool,
+ name: &Ident,
+ ty: &TokenStream2,
+ ceiling: u8,
+ ptr: &TokenStream2,
+) -> TokenStream2 {
+ let path = if resources_prefix {
+ quote!(shared_resources::#name)
+ } else {
+ quote!(#name)
+ };
+
+ let device = &app.args.device;
+ let masks_name = priority_masks_ident();
+ quote!(
+ #(#cfgs)*
+ impl<'a> rtic::Mutex for #path<'a> {
+ type T = #ty;
+
+ #[inline(always)]
+ fn lock<RTIC_INTERNAL_R>(&mut self, f: impl FnOnce(&mut #ty) -> RTIC_INTERNAL_R) -> RTIC_INTERNAL_R {
+ /// Priority ceiling
+ const CEILING: u8 = #ceiling;
+
+ unsafe {
+ rtic::export::lock(
+ #ptr,
+ CEILING,
+ #device::NVIC_PRIO_BITS,
+ &#masks_name,
+ f,
+ )
+ }
+ }
+ }
+ )
+}
+
+pub fn interrupt_ident() -> Ident {
+ let span = Span::call_site();
+ Ident::new("interrupt", span)
+}
+
+/// Whether `name` is an exception with configurable priority
+pub fn is_exception(name: &Ident) -> bool {
+ let s = name.to_string();
+
+ matches!(
+ &*s,
+ "MemoryManagement"
+ | "BusFault"
+ | "UsageFault"
+ | "SecureFault"
+ | "SVCall"
+ | "DebugMonitor"
+ | "PendSV"
+ | "SysTick"
+ )
+}
+
+/// Mark a name as internal
+pub fn mark_internal_name(name: &str) -> Ident {
+ Ident::new(&format!("{RTIC_INTERNAL}_{name}"), Span::call_site())
+}
+
+/// Generate an internal identifier for tasks
+pub fn internal_task_ident(task: &Ident, ident_name: &str) -> Ident {
+ mark_internal_name(&format!("{task}_{ident_name}"))
+}
+
+fn link_section_index() -> usize {
+ static INDEX: AtomicUsize = AtomicUsize::new(0);
+
+ INDEX.fetch_add(1, Ordering::Relaxed)
+}
+
+/// Add `link_section` attribute
+pub fn link_section_uninit() -> TokenStream2 {
+ let section = format!(".uninit.rtic{}", link_section_index());
+
+ quote!(#[link_section = #section])
+}
+
+/// Regroups the inputs of a task
+///
+/// `inputs` could be &[`input: Foo`] OR &[`mut x: i32`, `ref y: i64`]
+pub fn regroup_inputs(
+ inputs: &[PatType],
+) -> (
+ // args e.g. &[`_0`], &[`_0: i32`, `_1: i64`]
+ Vec<TokenStream2>,
+ // tupled e.g. `_0`, `(_0, _1)`
+ TokenStream2,
+ // untupled e.g. &[`_0`], &[`_0`, `_1`]
+ Vec<TokenStream2>,
+ // ty e.g. `Foo`, `(i32, i64)`
+ TokenStream2,
+) {
+ if inputs.len() == 1 {
+ let ty = &inputs[0].ty;
+
+ (
+ vec![quote!(_0: #ty)],
+ quote!(_0),
+ vec![quote!(_0)],
+ quote!(#ty),
+ )
+ } else {
+ let mut args = vec![];
+ let mut pats = vec![];
+ let mut tys = vec![];
+
+ for (i, input) in inputs.iter().enumerate() {
+ let i = Ident::new(&format!("_{i}"), Span::call_site());
+ let ty = &input.ty;
+
+ args.push(quote!(#i: #ty));
+
+ pats.push(quote!(#i));
+
+ tys.push(quote!(#ty));
+ }
+
+ let tupled = {
+ let pats = pats.clone();
+ quote!((#(#pats,)*))
+ };
+ let ty = quote!((#(#tys,)*));
+ (args, tupled, pats, ty)
+ }
+}
+
+/// Get the ident for the name of the task
+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()
+ .expect("RTIC-ICE: unable to find idle name")
+ .name
+ .to_string(),
+ Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(),
+ };
+
+ Ident::new(&s, Span::call_site())
+}
+
+/// Generates a pre-reexport identifier for the "shared resources" struct
+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()
+ .expect("RTIC-ICE: unable to find idle name")
+ .name
+ .to_string(),
+ Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(),
+ };
+
+ s.push_str("SharedResources");
+
+ mark_internal_name(&s)
+}
+
+/// Generates a pre-reexport identifier for the "local resources" struct
+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()
+ .expect("RTIC-ICE: unable to find idle name")
+ .name
+ .to_string(),
+ Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(),
+ };
+
+ s.push_str("LocalResources");
+
+ mark_internal_name(&s)
+}
+
+/// Suffixed identifier
+pub fn suffixed(name: &str) -> Ident {
+ let span = Span::call_site();
+ Ident::new(name, span)
+}
+
+pub fn static_shared_resource_ident(name: &Ident) -> Ident {
+ mark_internal_name(&format!("shared_resource_{name}"))
+}
+
+/// Generates an Ident for the number of 32 bit chunks used for Mask storage.
+pub fn priority_mask_chunks_ident() -> Ident {
+ mark_internal_name("MASK_CHUNKS")
+}
+
+pub fn priority_masks_ident() -> Ident {
+ mark_internal_name("MASKS")
+}
+
+pub fn static_local_resource_ident(name: &Ident) -> Ident {
+ mark_internal_name(&format!("local_resource_{name}"))
+}
+
+pub fn declared_static_local_resource_ident(name: &Ident, task_name: &Ident) -> Ident {
+ mark_internal_name(&format!("local_{task_name}_{name}"))
+}
+
+pub fn need_to_lock_ident(name: &Ident) -> Ident {
+ Ident::new(&format!("{name}_that_needs_to_be_locked"), name.span())
+}
+
+pub fn zero_prio_dispatcher_ident() -> Ident {
+ Ident::new("__rtic_internal_async_0_prio_dispatcher", Span::call_site())
+}
+
+/// The name to get better RT flag errors
+pub fn rt_err_ident() -> Ident {
+ Ident::new(
+ "you_must_enable_the_rt_feature_for_the_pac_in_your_cargo_toml",
+ Span::call_site(),
+ )
+}
diff --git a/rtic-macros/src/lib.rs b/rtic-macros/src/lib.rs
new file mode 100644
index 00000000..3ac27017
--- /dev/null
+++ b/rtic-macros/src/lib.rs
@@ -0,0 +1,91 @@
+#![doc(
+ 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
+
+use proc_macro::TokenStream;
+use std::{env, fs, path::Path};
+
+mod analyze;
+mod bindings;
+mod check;
+mod codegen;
+mod syntax;
+
+// Used for mocking the API in testing
+#[doc(hidden)]
+#[proc_macro_attribute]
+pub fn mock_app(args: TokenStream, input: TokenStream) -> TokenStream {
+ if let Err(e) = syntax::parse(args, input) {
+ e.to_compile_error().into()
+ } else {
+ "fn main() {}".parse().unwrap()
+ }
+}
+
+/// Attribute used to declare a RTIC application
+///
+/// For user documentation see the [RTIC book](https://rtic.rs)
+///
+/// # Panics
+///
+/// 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 (app, analysis) = match syntax::parse(args, input) {
+ Err(e) => return e.to_compile_error().into(),
+ Ok(x) => x,
+ };
+
+ if let Err(e) = check::app(&app) {
+ return e.to_compile_error().into();
+ }
+
+ let analysis = analyze::app(analysis, &app);
+
+ let ts = codegen::app(&app, &analysis);
+
+ // Default output path: <project_dir>/target/
+ let mut out_dir = Path::new("target");
+
+ // Get output directory from Cargo environment
+ // 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());
+
+ if !out_dir.exists() {
+ // Set out_dir to OUT_DIR
+ out_dir = Path::new(&out_str);
+
+ // Default build path, annotated below:
+ // $(pwd)/target/thumbv7em-none-eabihf/debug/build/rtic-<HASH>/out/
+ // <project_dir>/<target-dir>/<TARGET>/debug/build/rtic-<HASH>/out/
+ //
+ // traverse up to first occurrence of TARGET, approximated with starts_with("thumbv")
+ // and use the parent() of this path
+ //
+ // 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() {
+ 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;
+ break;
+ }
+ // If no parent, just use it
+ out_dir = path;
+ break;
+ }
+ }
+ }
+ }
+
+ // Try to write the expanded code to disk
+ if let Some(out_str) = out_dir.to_str() {
+ fs::write(format!("{out_str}/rtic-expansion.rs"), ts.to_string()).ok();
+ }
+
+ ts.into()
+}
diff --git a/rtic-macros/src/syntax.rs b/rtic-macros/src/syntax.rs
new file mode 100644
index 00000000..d6f5a476
--- /dev/null
+++ b/rtic-macros/src/syntax.rs
@@ -0,0 +1,121 @@
+#[allow(unused_extern_crates)]
+extern crate proc_macro;
+
+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 parse;
+
+/// An ordered map keyed by identifier
+pub type Map<T> = IndexMap<Ident, T>;
+
+/// An order set
+pub type Set<T> = IndexSet<T>;
+
+/// Execution context
+#[derive(Clone, Copy)]
+pub enum Context<'a> {
+ /// The `idle` context
+ Idle,
+
+ /// The `init`-ialization function
+ Init,
+
+ /// A async software task
+ SoftwareTask(&'a Ident),
+
+ /// A hardware task
+ 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()
+ }
+ }
+ }
+}
+
+/// Parses the input of the `#[app]` attribute
+pub fn parse(
+ args: TokenStream,
+ input: TokenStream,
+) -> Result<(ast::App, analyze::Analysis), syn::parse::Error> {
+ parse2(args.into(), input.into())
+}
+
+/// `proc_macro2::TokenStream` version of `parse`
+pub fn parse2(
+ args: TokenStream2,
+ input: TokenStream2,
+) -> Result<(ast::App, analyze::Analysis), syn::parse::Error> {
+ let app = parse::app(args, input)?;
+ check::app(&app)?;
+
+ 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/rtic-macros/src/syntax/.travis.yml b/rtic-macros/src/syntax/.travis.yml
new file mode 100644
index 00000000..52d1ffdd
--- /dev/null
+++ b/rtic-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/rtic-macros/src/syntax/accessors.rs b/rtic-macros/src/syntax/accessors.rs
new file mode 100644
index 00000000..e75dde6c
--- /dev/null
+++ b/rtic-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/rtic-macros/src/syntax/analyze.rs b/rtic-macros/src/syntax/analyze.rs
new file mode 100644
index 00000000..57f9f2cd
--- /dev/null
+++ b/rtic-macros/src/syntax/analyze.rs
@@ -0,0 +1,414 @@
+//! 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 = Ident;
+ type Priority = u8;
+
+ // The task list is a Tuple (Name, Shared Resources, Local Resources, Priority)
+ let task_resources_list: Vec<(TaskName, Vec<&Ident>, &LocalResources, Priority)> =
+ Some(&app.init)
+ .iter()
+ .map(|ht| (ht.name.clone(), Vec::new(), &ht.args.local_resources, 0))
+ .chain(app.idle.iter().map(|ht| {
+ (
+ ht.name.clone(),
+ ht.args
+ .shared_resources
+ .iter()
+ .map(|(v, _)| v)
+ .collect::<Vec<_>>(),
+ &ht.args.local_resources,
+ 0,
+ )
+ }))
+ .chain(app.software_tasks.iter().map(|(name, ht)| {
+ (
+ name.clone(),
+ ht.args
+ .shared_resources
+ .iter()
+ .map(|(v, _)| v)
+ .collect::<Vec<_>>(),
+ &ht.args.local_resources,
+ ht.args.priority,
+ )
+ }))
+ .chain(app.hardware_tasks.iter().map(|(name, ht)| {
+ (
+ name.clone(),
+ ht.args
+ .shared_resources
+ .iter()
+ .map(|(v, _)| v)
+ .collect::<Vec<_>>(),
+ &ht.args.local_resources,
+ ht.args.priority,
+ )
+ }))
+ .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) in task_resources_list.iter() {
+ for r in tr {
+ // Get all uses of resources annotated lock_free
+ if lf_res == r {
+ // Check so async tasks do not use lock free resources
+ if app.software_tasks.get(task).is_some() {
+ 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!(
+ "Async task {:?} has priority 0, but `#[idle]` is defined. 0-priority async tasks are only allowed if there is no `#[idle]`.",
+ 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`, only 0 prio does not need it
+ let mut send_types = SendTypes::new();
+
+ for (name, res) in app.shared_resources.iter() {
+ if ownerships
+ .get(name)
+ .map(|ownership| match *ownership {
+ Ownership::Owned { priority: ceiling }
+ | Ownership::CoOwned { priority: ceiling }
+ | Ownership::Contended { ceiling } => ceiling != 0,
+ })
+ .unwrap_or(false)
+ {
+ send_types.insert(res.ty.clone());
+ }
+ }
+
+ // Most local resources need to be `Send` as well, only 0 prio does not need it
+ for (name, res) in app.local_resources.iter() {
+ if ownerships
+ .get(name)
+ .map(|ownership| match *ownership {
+ Ownership::Owned { priority: ceiling }
+ | Ownership::CoOwned { priority: ceiling }
+ | Ownership::Contended { ceiling } => ceiling != 0,
+ })
+ .unwrap_or(false)
+ {
+ 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());
+
+ // All inputs are send as we do not know from where they may be spawned.
+ 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()));
+
+ 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 {
+ /// 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/rtic-macros/src/syntax/ast.rs b/rtic-macros/src/syntax/ast.rs
new file mode 100644
index 00000000..27e6773f
--- /dev/null
+++ b/rtic-macros/src/syntax/ast.rs
@@ -0,0 +1,335 @@
+//! 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>,
+
+ /// 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>,
+
+ /// Async 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>,
+}
+
+/// An async 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,
+}
+
+/// Software task metadata
+#[derive(Debug)]
+#[non_exhaustive]
+pub struct SoftwareTaskArgs {
+ /// 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,
+}
+
+impl Default for SoftwareTaskArgs {
+ fn default() -> Self {
+ Self {
+ priority: 1,
+ local_resources: LocalResources::new(),
+ shared_resources: SharedResources::new(),
+ }
+ }
+}
+
+/// 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/rtic-macros/src/syntax/check.rs b/rtic-macros/src/syntax/check.rs
new file mode 100644
index 00000000..989d4180
--- /dev/null
+++ b/rtic-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/rtic-macros/src/syntax/optimize.rs b/rtic-macros/src/syntax/optimize.rs
new file mode 100644
index 00000000..e83ba31b
--- /dev/null
+++ b/rtic-macros/src/syntax/optimize.rs
@@ -0,0 +1,36 @@
+use std::collections::{BTreeSet, HashMap};
+
+use crate::syntax::ast::App;
+
+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/rtic-macros/src/syntax/parse.rs b/rtic-macros/src/syntax/parse.rs
new file mode 100644
index 00000000..72eeeaf6
--- /dev/null
+++ b/rtic-macros/src/syntax/parse.rs
@@ -0,0 +1,319 @@
+mod app;
+mod hardware_task;
+mod idle;
+mod init;
+mod resource;
+mod software_task;
+mod util;
+
+use proc_macro2::TokenStream as TokenStream2;
+use syn::{
+ braced, parenthesized,
+ parse::{self, Parse, ParseStream, Parser},
+ token::Brace,
+ Ident, Item, LitInt, Token,
+};
+
+use crate::syntax::{
+ ast::{App, AppArgs, HardwareTaskArgs, IdleArgs, InitArgs, SoftwareTaskArgs, TaskLocal},
+ Either,
+};
+
+// Parse the app, both app arguments and body (input)
+pub fn app(args: TokenStream2, input: TokenStream2) -> parse::Result<App> {
+ let args = AppArgs::parse(args)?;
+ let input: Input = syn::parse2(input)?;
+
+ App::parse(args, input)
+}
+
+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) -> 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 priority = None;
+ let mut shared_resources = None;
+ let mut local_resources = None;
+ let mut 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();
+
+ // Handle equal sign
+ let _: Token![=] = content.parse()?;
+
+ match &*ident_s {
+ "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() {
+ 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",
+ ));
+ }
+
+ Either::Left(HardwareTaskArgs {
+ binds,
+ priority,
+ shared_resources,
+ local_resources,
+ })
+ } else {
+ Either::Right(SoftwareTaskArgs {
+ priority,
+ shared_resources,
+ local_resources,
+ })
+ })
+ })
+ .parse2(tokens)
+}
diff --git a/rtic-macros/src/syntax/parse/app.rs b/rtic-macros/src/syntax/parse/app.rs
new file mode 100644
index 00000000..e797f75e
--- /dev/null
+++ b/rtic-macros/src/syntax/parse/app.rs
@@ -0,0 +1,480 @@
+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, Visibility,
+};
+
+use super::Input;
+use crate::syntax::{
+ ast::{
+ App, AppArgs, Dispatcher, Dispatchers, HardwareTask, Idle, IdleArgs, Init, InitArgs,
+ LocalResource, SharedResource, SoftwareTask,
+ },
+ parse::{self as syntax_parse, util},
+ Either, Map, Set,
+};
+
+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) -> 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 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 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(())
+ };
+
+ 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)? {
+ 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)? {
+ 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());
+ }
+ _ => {
+ // 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,
+ shared_resources,
+ local_resources,
+ user_imports,
+ user_code,
+ hardware_tasks,
+ software_tasks,
+ })
+ }
+}
diff --git a/rtic-macros/src/syntax/parse/hardware_task.rs b/rtic-macros/src/syntax/parse/hardware_task.rs
new file mode 100644
index 00000000..7f6dfbe4
--- /dev/null
+++ b/rtic-macros/src/syntax/parse/hardware_task.rs
@@ -0,0 +1,76 @@
+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 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({name}::Context)`"),
+ ))
+ }
+}
+
+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 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({name}::Context)`"),
+ ))
+ }
+}
diff --git a/rtic-macros/src/syntax/parse/idle.rs b/rtic-macros/src/syntax/parse/idle.rs
new file mode 100644
index 00000000..124c1366
--- /dev/null
+++ b/rtic-macros/src/syntax/parse/idle.rs
@@ -0,0 +1,42 @@
+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({name}::Context) -> !`"),
+ ))
+ }
+}
diff --git a/rtic-macros/src/syntax/parse/init.rs b/rtic-macros/src/syntax/parse/init.rs
new file mode 100644
index 00000000..0aea20bd
--- /dev/null
+++ b/rtic-macros/src/syntax/parse/init.rs
@@ -0,0 +1,51 @@
+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)
+ {
+ 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({name}::Context) -> (Shared resources struct, Local resources struct)`"
+ ),
+ ))
+ }
+}
diff --git a/rtic-macros/src/syntax/parse/resource.rs b/rtic-macros/src/syntax/parse/resource.rs
new file mode 100644
index 00000000..ff100576
--- /dev/null
+++ b/rtic-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/rtic-macros/src/syntax/parse/software_task.rs b/rtic-macros/src/syntax/parse/software_task.rs
new file mode 100644
index 00000000..769aa653
--- /dev/null
+++ b/rtic-macros/src/syntax/parse/software_task.rs
@@ -0,0 +1,76 @@
+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)
+ && item.sig.asyncness.is_some();
+
+ let span = item.sig.ident.span();
+
+ let name = item.sig.ident.to_string();
+
+ 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,
+ });
+ }
+ }
+
+ Err(parse::Error::new(
+ span,
+ format!("this task handler must have type signature `async fn({name}::Context, ..)`"),
+ ))
+ }
+}
+
+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)
+ && item.sig.asyncness.is_some();
+
+ let span = item.sig.ident.span();
+
+ let name = item.sig.ident.to_string();
+
+ 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,
+ });
+ }
+ }
+
+ Err(parse::Error::new(
+ span,
+ format!("this task handler must have type signature `async fn({name}::Context, ..)`"),
+ ))
+ }
+}
diff --git a/rtic-macros/src/syntax/parse/util.rs b/rtic-macros/src/syntax/parse/util.rs
new file mode 100644
index 00000000..5a5e0c0e
--- /dev/null
+++ b/rtic-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) -> 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)
+ //
+ // We check the length and the last one here, analysis checks that the user
+ // provided structs are correct.
+ if t.elems.len() == 2 {
+ 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
+ }
+}