aboutsummaryrefslogtreecommitdiff
path: root/macros/src/codegen
diff options
context:
space:
mode:
Diffstat (limited to 'macros/src/codegen')
-rw-r--r--macros/src/codegen/assertions.rs19
-rw-r--r--macros/src/codegen/dispatchers.rs155
-rw-r--r--macros/src/codegen/hardware_tasks.rs134
-rw-r--r--macros/src/codegen/idle.rs104
-rw-r--r--macros/src/codegen/init.rs125
-rw-r--r--macros/src/codegen/locals.rs94
-rw-r--r--macros/src/codegen/module.rs330
-rw-r--r--macros/src/codegen/post_init.rs31
-rw-r--r--macros/src/codegen/pre_init.rs109
-rw-r--r--macros/src/codegen/resources.rs122
-rw-r--r--macros/src/codegen/resources_struct.rs177
-rw-r--r--macros/src/codegen/schedule.rs90
-rw-r--r--macros/src/codegen/schedule_body.rs59
-rw-r--r--macros/src/codegen/software_tasks.rs169
-rw-r--r--macros/src/codegen/spawn.rs121
-rw-r--r--macros/src/codegen/spawn_body.rs76
-rw-r--r--macros/src/codegen/timer_queue.rs137
-rw-r--r--macros/src/codegen/util.rs247
18 files changed, 2299 insertions, 0 deletions
diff --git a/macros/src/codegen/assertions.rs b/macros/src/codegen/assertions.rs
new file mode 100644
index 00000000..4d9aae47
--- /dev/null
+++ b/macros/src/codegen/assertions.rs
@@ -0,0 +1,19 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+
+use crate::analyze::Analysis;
+
+/// Generates compile-time assertions that check that types implement the `Send` / `Sync` traits
+pub fn codegen(analysis: &Analysis) -> Vec<TokenStream2> {
+ let mut stmts = vec![];
+
+ for ty in &analysis.send_types {
+ stmts.push(quote!(rtic::export::assert_send::<#ty>();));
+ }
+
+ for ty in &analysis.sync_types {
+ stmts.push(quote!(rtic::export::assert_sync::<#ty>();));
+ }
+
+ stmts
+}
diff --git a/macros/src/codegen/dispatchers.rs b/macros/src/codegen/dispatchers.rs
new file mode 100644
index 00000000..300aa996
--- /dev/null
+++ b/macros/src/codegen/dispatchers.rs
@@ -0,0 +1,155 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtic_syntax::ast::App;
+
+use crate::{analyze::Analysis, check::Extra, codegen::util};
+
+/// Generates task dispatchers
+pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec<TokenStream2> {
+ let mut items = vec![];
+
+ let interrupts = &analysis.interrupts;
+
+ for (&level, channel) in &analysis.channels {
+ let mut stmts = vec![];
+
+ let variants = channel
+ .tasks
+ .iter()
+ .map(|name| {
+ let cfgs = &app.software_tasks[name].cfgs;
+
+ quote!(
+ #(#cfgs)*
+ #name
+ )
+ })
+ .collect::<Vec<_>>();
+
+ let doc = format!(
+ "Software tasks to be dispatched at priority level {}",
+ level,
+ );
+ let t = util::spawn_t_ident(level);
+ items.push(quote!(
+ #[allow(non_camel_case_types)]
+ #[derive(Clone, Copy)]
+ #[doc = #doc]
+ enum #t {
+ #(#variants,)*
+ }
+ ));
+
+ let n = util::capacity_typenum(channel.capacity, true);
+ let rq = util::rq_ident(level);
+ let (rq_ty, rq_expr) = {
+ (
+ quote!(rtic::export::SCRQ<#t, #n>),
+ quote!(rtic::export::Queue(unsafe {
+ rtic::export::iQueue::u8_sc()
+ })),
+ )
+ };
+
+ let doc = format!(
+ "Queue of tasks ready to be dispatched at priority level {}",
+ level
+ );
+ items.push(quote!(
+ #[doc = #doc]
+ static mut #rq: #rq_ty = #rq_expr;
+ ));
+
+ if let Some(ceiling) = channel.ceiling {
+ items.push(quote!(
+ struct #rq<'a> {
+ priority: &'a rtic::export::Priority,
+ }
+ ));
+
+ items.push(util::impl_mutex(
+ extra,
+ &[],
+ false,
+ &rq,
+ rq_ty,
+ ceiling,
+ quote!(&mut #rq),
+ ));
+ }
+
+ let arms = channel
+ .tasks
+ .iter()
+ .map(|name| {
+ let task = &app.software_tasks[name];
+ let cfgs = &task.cfgs;
+ let fq = util::fq_ident(name);
+ let inputs = util::inputs_ident(name);
+ let (_, tupled, pats, _) = util::regroup_inputs(&task.inputs);
+
+ let (let_instant, instant) = if app.uses_schedule() {
+ let instants = util::instants_ident(name);
+
+ (
+ quote!(
+ let instant =
+ #instants.get_unchecked(usize::from(index)).as_ptr().read();
+ ),
+ quote!(, instant),
+ )
+ } else {
+ (quote!(), quote!())
+ };
+
+ let locals_new = if task.locals.is_empty() {
+ quote!()
+ } else {
+ quote!(#name::Locals::new(),)
+ };
+
+ quote!(
+ #(#cfgs)*
+ #t::#name => {
+ let #tupled =
+ #inputs.get_unchecked(usize::from(index)).as_ptr().read();
+ #let_instant
+ #fq.split().0.enqueue_unchecked(index);
+ let priority = &rtic::export::Priority::new(PRIORITY);
+ crate::#name(
+ #locals_new
+ #name::Context::new(priority #instant)
+ #(,#pats)*
+ )
+ }
+ )
+ })
+ .collect::<Vec<_>>();
+
+ stmts.push(quote!(
+ while let Some((task, index)) = #rq.split().1.dequeue() {
+ match task {
+ #(#arms)*
+ }
+ }
+ ));
+
+ let doc = format!("Interrupt handler to dispatch tasks at priority {}", level);
+ let interrupt = util::suffixed(&interrupts[&level].to_string());
+ items.push(quote!(
+ #[allow(non_snake_case)]
+ #[doc = #doc]
+ #[no_mangle]
+ unsafe fn #interrupt() {
+ /// The priority of this interrupt handler
+ const PRIORITY: u8 = #level;
+
+ rtic::export::run(PRIORITY, || {
+ #(#stmts)*
+ });
+ }
+ ));
+ }
+
+ items
+}
diff --git a/macros/src/codegen/hardware_tasks.rs b/macros/src/codegen/hardware_tasks.rs
new file mode 100644
index 00000000..25f1df41
--- /dev/null
+++ b/macros/src/codegen/hardware_tasks.rs
@@ -0,0 +1,134 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::{format_ident, quote};
+use rtic_syntax::{ast::App, Context};
+
+use crate::{
+ analyze::Analysis,
+ check::Extra,
+ codegen::{locals, module, resources_struct},
+};
+
+/// Generate support code for hardware tasks (`#[exception]`s and `#[interrupt]`s)
+pub fn codegen(
+ app: &App,
+ analysis: &Analysis,
+ extra: &Extra,
+) -> (
+ // mod_app_hardware_tasks -- interrupt handlers and `${task}Resources` constructors
+ Vec<TokenStream2>,
+ // root_hardware_tasks -- items that must be placed in the root of the crate:
+ // - `${task}Locals` structs
+ // - `${task}Resources` structs
+ // - `${task}` modules
+ Vec<TokenStream2>,
+ // user_hardware_tasks -- the `#[task]` functions written by the user
+ Vec<TokenStream2>,
+ // user_hardware_tasks_imports -- the imports for `#[task]` functions written by the user
+ Vec<TokenStream2>,
+) {
+ let mut mod_app = vec![];
+ let mut root = vec![];
+ let mut user_tasks = vec![];
+ let mut hardware_tasks_imports = vec![];
+
+ for (name, task) in &app.hardware_tasks {
+ let (let_instant, instant) = if app.uses_schedule() {
+ let m = extra.monotonic();
+
+ (
+ Some(quote!(let instant = <#m as rtic::Monotonic>::now();)),
+ Some(quote!(, instant)),
+ )
+ } else {
+ (None, None)
+ };
+
+ let locals_new = if task.locals.is_empty() {
+ quote!()
+ } else {
+ quote!(#name::Locals::new(),)
+ };
+
+ let symbol = task.args.binds.clone();
+ let priority = task.args.priority;
+
+ mod_app.push(quote!(
+ #[allow(non_snake_case)]
+ #[no_mangle]
+ unsafe fn #symbol() {
+ const PRIORITY: u8 = #priority;
+
+ #let_instant
+
+ rtic::export::run(PRIORITY, || {
+ crate::#name(
+ #locals_new
+ #name::Context::new(&rtic::export::Priority::new(PRIORITY) #instant)
+ )
+ });
+ }
+ ));
+
+ let mut needs_lt = false;
+
+ // `${task}Resources`
+ if !task.args.resources.is_empty() {
+ let (item, constructor) = resources_struct::codegen(
+ Context::HardwareTask(name),
+ priority,
+ &mut needs_lt,
+ app,
+ analysis,
+ );
+
+ // Add resources to imports
+ let name_res = format_ident!("{}Resources", name);
+ hardware_tasks_imports.push(quote!(
+ #[allow(non_snake_case)]
+ use super::#name_res;
+ ));
+
+ root.push(item);
+
+ mod_app.push(constructor);
+ }
+
+ root.push(module::codegen(
+ Context::HardwareTask(name),
+ needs_lt,
+ app,
+ extra,
+ ));
+
+ // `${task}Locals`
+ let mut locals_pat = None;
+ if !task.locals.is_empty() {
+ let (struct_, pat) = locals::codegen(Context::HardwareTask(name), &task.locals, app);
+
+ root.push(struct_);
+ locals_pat = Some(pat);
+ }
+
+ let attrs = &task.attrs;
+ let context = &task.context;
+ let stmts = &task.stmts;
+ let locals_pat = locals_pat.iter();
+ user_tasks.push(quote!(
+ #(#attrs)*
+ #[allow(non_snake_case)]
+ fn #name(#(#locals_pat,)* #context: #name::Context) {
+ use rtic::Mutex as _;
+
+ #(#stmts)*
+ }
+ ));
+
+ hardware_tasks_imports.push(quote!(
+ #(#attrs)*
+ #[allow(non_snake_case)]
+ use super::#name;
+ ));
+ }
+
+ (mod_app, root, user_tasks, hardware_tasks_imports)
+}
diff --git a/macros/src/codegen/idle.rs b/macros/src/codegen/idle.rs
new file mode 100644
index 00000000..2e2932d7
--- /dev/null
+++ b/macros/src/codegen/idle.rs
@@ -0,0 +1,104 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::{format_ident, quote};
+use rtic_syntax::{ast::App, Context};
+
+use crate::{
+ analyze::Analysis,
+ check::Extra,
+ codegen::{locals, module, resources_struct},
+};
+
+/// Generates support code for `#[idle]` functions
+pub fn codegen(
+ app: &App,
+ analysis: &Analysis,
+ extra: &Extra,
+) -> (
+ // mod_app_idle -- the `${idle}Resources` constructor
+ Option<TokenStream2>,
+ // root_idle -- items that must be placed in the root of the crate:
+ // - the `${idle}Locals` struct
+ // - the `${idle}Resources` struct
+ // - the `${idle}` module, which contains types like `${idle}::Context`
+ Vec<TokenStream2>,
+ // user_idle
+ Option<TokenStream2>,
+ // user_idle_imports
+ Vec<TokenStream2>,
+ // call_idle
+ TokenStream2,
+) {
+ if app.idles.len() > 0 {
+ let idle = &app.idles.first().unwrap();
+ let mut needs_lt = false;
+ let mut mod_app = None;
+ let mut root_idle = vec![];
+ let mut locals_pat = None;
+ let mut locals_new = None;
+
+ let mut user_idle_imports = vec![];
+
+ let name = &idle.name;
+
+ if !idle.args.resources.is_empty() {
+ let (item, constructor) =
+ resources_struct::codegen(Context::Idle, 0, &mut needs_lt, app, analysis);
+
+ root_idle.push(item);
+ mod_app = Some(constructor);
+
+ let name_resource = format_ident!("{}Resources", name);
+ user_idle_imports.push(quote!(
+ #[allow(non_snake_case)]
+ use super::#name_resource;
+ ));
+ }
+
+ if !idle.locals.is_empty() {
+ let (locals, pat) = locals::codegen(Context::Idle, &idle.locals, app);
+
+ locals_new = Some(quote!(#name::Locals::new()));
+ locals_pat = Some(pat);
+ root_idle.push(locals);
+ }
+
+ root_idle.push(module::codegen(Context::Idle, needs_lt, app, extra));
+
+ let attrs = &idle.attrs;
+ let context = &idle.context;
+ let stmts = &idle.stmts;
+ let locals_pat = locals_pat.iter();
+ let user_idle = Some(quote!(
+ #(#attrs)*
+ #[allow(non_snake_case)]
+ fn #name(#(#locals_pat,)* #context: #name::Context) -> ! {
+ use rtic::Mutex as _;
+
+ #(#stmts)*
+ }
+ ));
+ user_idle_imports.push(quote!(
+ #(#attrs)*
+ #[allow(non_snake_case)]
+ use super::#name;
+ ));
+
+ let locals_new = locals_new.iter();
+ let call_idle = quote!(crate::#name(
+ #(#locals_new,)*
+ #name::Context::new(&rtic::export::Priority::new(0))
+ ));
+
+ (mod_app, root_idle, user_idle, user_idle_imports, call_idle)
+ } else {
+ (
+ None,
+ vec![],
+ None,
+ vec![],
+ quote!(loop {
+ rtic::export::wfi()
+ }),
+ )
+ }
+}
diff --git a/macros/src/codegen/init.rs b/macros/src/codegen/init.rs
new file mode 100644
index 00000000..8942439b
--- /dev/null
+++ b/macros/src/codegen/init.rs
@@ -0,0 +1,125 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::{format_ident, quote};
+use rtic_syntax::{ast::App, Context};
+
+use crate::{
+ analyze::Analysis,
+ check::Extra,
+ codegen::{locals, module, resources_struct, util},
+};
+
+/// Generates support code for `#[init]` functions
+pub fn codegen(
+ app: &App,
+ analysis: &Analysis,
+ extra: &Extra,
+) -> (
+ // mod_app_idle -- the `${init}Resources` constructor
+ Option<TokenStream2>,
+ // root_init -- items that must be placed in the root of the crate:
+ // - the `${init}Locals` struct
+ // - the `${init}Resources` struct
+ // - the `${init}LateResources` struct
+ // - the `${init}` module, which contains types like `${init}::Context`
+ Vec<TokenStream2>,
+ // user_init -- the `#[init]` function written by the user
+ Option<TokenStream2>,
+ // user_init_imports -- the imports for `#[init]` functio written by the user
+ Vec<TokenStream2>,
+ // call_init -- the call to the user `#[init]` if there's one
+ Option<TokenStream2>,
+) {
+ if app.inits.len() > 0 {
+ let init = &app.inits.first().unwrap();
+ let mut needs_lt = false;
+ let name = &init.name;
+
+ let mut root_init = vec![];
+
+ let late_fields = analysis
+ .late_resources
+ .iter()
+ .flat_map(|resources| {
+ resources.iter().map(|name| {
+ let ty = &app.late_resources[name].ty;
+ let cfgs = &app.late_resources[name].cfgs;
+
+ quote!(
+ #(#cfgs)*
+ pub #name: #ty
+ )
+ })
+ })
+ .collect::<Vec<_>>();
+
+ let mut user_init_imports = vec![];
+ let late_resources = util::late_resources_ident(&name);
+
+ root_init.push(quote!(
+ /// Resources initialized at runtime
+ #[allow(non_snake_case)]
+ pub struct #late_resources {
+ #(#late_fields),*
+ }
+ ));
+
+ let name_late = format_ident!("{}LateResources", name);
+ user_init_imports.push(quote!(
+ #[allow(non_snake_case)]
+ use super::#name_late;
+ ));
+
+ let mut locals_pat = None;
+ let mut locals_new = None;
+ if !init.locals.is_empty() {
+ let (struct_, pat) = locals::codegen(Context::Init, &init.locals, app);
+
+ locals_new = Some(quote!(#name::Locals::new()));
+ locals_pat = Some(pat);
+ root_init.push(struct_);
+ }
+
+ let context = &init.context;
+ let attrs = &init.attrs;
+ let stmts = &init.stmts;
+ let locals_pat = locals_pat.iter();
+ let user_init = Some(quote!(
+ #(#attrs)*
+ #[allow(non_snake_case)]
+ fn #name(#(#locals_pat,)* #context: #name::Context) -> #name::LateResources {
+ #(#stmts)*
+ }
+ ));
+ user_init_imports.push(quote!(
+ #(#attrs)*
+ #[allow(non_snake_case)]
+ use super::#name;
+ ));
+
+ let mut mod_app = None;
+ if !init.args.resources.is_empty() {
+ let (item, constructor) =
+ resources_struct::codegen(Context::Init, 0, &mut needs_lt, app, analysis);
+
+ root_init.push(item);
+ mod_app = Some(constructor);
+
+ let name_late = format_ident!("{}Resources", name);
+ user_init_imports.push(quote!(
+ #[allow(non_snake_case)]
+ use super::#name_late;
+ ));
+ }
+
+ let locals_new = locals_new.iter();
+ let call_init = Some(
+ quote!(let late = crate::#name(#(#locals_new,)* #name::Context::new(core.into()));),
+ );
+
+ root_init.push(module::codegen(Context::Init, needs_lt, app, extra));
+
+ (mod_app, root_init, user_init, user_init_imports, call_init)
+ } else {
+ (None, vec![], None, vec![], None)
+ }
+}
diff --git a/macros/src/codegen/locals.rs b/macros/src/codegen/locals.rs
new file mode 100644
index 00000000..336c0b21
--- /dev/null
+++ b/macros/src/codegen/locals.rs
@@ -0,0 +1,94 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtic_syntax::{
+ ast::{App, Local},
+ Context, Map,
+};
+
+use crate::codegen::util;
+
+pub fn codegen(
+ ctxt: Context,
+ locals: &Map<Local>,
+ app: &App,
+) -> (
+ // locals
+ TokenStream2,
+ // pat
+ TokenStream2,
+) {
+ assert!(!locals.is_empty());
+
+ let runs_once = ctxt.runs_once();
+ let ident = util::locals_ident(ctxt, app);
+
+ let mut lt = None;
+ let mut fields = vec![];
+ let mut items = vec![];
+ let mut names = vec![];
+ let mut values = vec![];
+ let mut pats = vec![];
+ let mut has_cfgs = false;
+
+ for (name, local) in locals {
+ let lt = if runs_once {
+ quote!('static)
+ } else {
+ lt = Some(quote!('a));
+ quote!('a)
+ };
+
+ let cfgs = &local.cfgs;
+ has_cfgs |= !cfgs.is_empty();
+
+ let expr = &local.expr;
+ let ty = &local.ty;
+ fields.push(quote!(
+ #(#cfgs)*
+ #name: &#lt mut #ty
+ ));
+ items.push(quote!(
+ #(#cfgs)*
+ static mut #name: #ty = #expr
+ ));
+ values.push(quote!(
+ #(#cfgs)*
+ #name: &mut #name
+ ));
+ names.push(name);
+ pats.push(quote!(
+ #(#cfgs)*
+ #name
+ ));
+ }
+
+ if lt.is_some() && has_cfgs {
+ fields.push(quote!(__marker__: core::marker::PhantomData<&'a mut ()>));
+ values.push(quote!(__marker__: core::marker::PhantomData));
+ }
+
+ let locals = quote!(
+ #[allow(non_snake_case)]
+ #[doc(hidden)]
+ pub struct #ident<#lt> {
+ #(#fields),*
+ }
+
+ impl<#lt> #ident<#lt> {
+ #[inline(always)]
+ unsafe fn new() -> Self {
+ #(#items;)*
+
+ #ident {
+ #(#values),*
+ }
+ }
+ }
+ );
+
+ let ident = ctxt.ident(app);
+ (
+ locals,
+ quote!(#ident::Locals { #(#pats,)* .. }: #ident::Locals),
+ )
+}
diff --git a/macros/src/codegen/module.rs b/macros/src/codegen/module.rs
new file mode 100644
index 00000000..2e51e7db
--- /dev/null
+++ b/macros/src/codegen/module.rs
@@ -0,0 +1,330 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtic_syntax::{ast::App, Context};
+
+use crate::{check::Extra, codegen::util};
+
+pub fn codegen(ctxt: Context, resources_tick: bool, app: &App, extra: &Extra) -> TokenStream2 {
+ let mut items = vec![];
+ let mut fields = vec![];
+ let mut values = vec![];
+
+ let name = ctxt.ident(app);
+
+ let mut needs_instant = false;
+ let mut lt = None;
+ match ctxt {
+ Context::Init => {
+ if app.uses_schedule() {
+ let m = extra.monotonic();
+
+ fields.push(quote!(
+ /// System start time = `Instant(0 /* cycles */)`
+ pub start: <#m as rtic::Monotonic>::Instant
+ ));
+
+ values.push(quote!(start: <#m as rtic::Monotonic>::zero()));
+
+ fields.push(quote!(
+ /// Core (Cortex-M) peripherals minus the SysTick
+ pub core: rtic::Peripherals
+ ));
+ } else {
+ fields.push(quote!(
+ /// Core (Cortex-M) peripherals
+ pub core: rtic::export::Peripherals
+ ));
+ }
+
+ if extra.peripherals {
+ let device = extra.device;
+
+ fields.push(quote!(
+ /// Device peripherals
+ pub device: #device::Peripherals
+ ));
+
+ values.push(quote!(device: #device::Peripherals::steal()));
+ }
+
+ lt = Some(quote!('a));
+ fields.push(quote!(
+ /// Critical section token for init
+ pub cs: rtic::export::CriticalSection<#lt>
+ ));
+
+ values.push(quote!(cs: rtic::export::CriticalSection::new()));
+
+ values.push(quote!(core));
+ }
+
+ Context::Idle => {}
+
+ Context::HardwareTask(..) => {
+ if app.uses_schedule() {
+ let m = extra.monotonic();
+
+ fields.push(quote!(
+ /// Time at which this handler started executing
+ pub start: <#m as rtic::Monotonic>::Instant
+ ));
+
+ values.push(quote!(start: instant));
+
+ needs_instant = true;
+ }
+ }
+
+ Context::SoftwareTask(..) => {
+ if app.uses_schedule() {
+ let m = extra.monotonic();
+
+ fields.push(quote!(
+ /// The time at which this task was scheduled to run
+ pub scheduled: <#m as rtic::Monotonic>::Instant
+ ));
+
+ values.push(quote!(scheduled: instant));
+
+ needs_instant = true;
+ }
+ }
+ }
+
+ if ctxt.has_locals(app) {
+ let ident = util::locals_ident(ctxt, app);
+ items.push(quote!(
+ #[doc(inline)]
+ pub use super::#ident as Locals;
+ ));
+ }
+
+ if ctxt.has_resources(app) {
+ let ident = util::resources_ident(ctxt, app);
+ let lt = if resources_tick {
+ lt = Some(quote!('a));
+ Some(quote!('a))
+ } else {
+ None
+ };
+
+ items.push(quote!(
+ #[doc(inline)]
+ pub use super::#ident as Resources;
+ ));
+
+ fields.push(quote!(
+ /// Resources this task has access to
+ pub resources: Resources<#lt>
+ ));
+
+ let priority = if ctxt.is_init() {
+ None
+ } else {
+ Some(quote!(priority))
+ };
+ values.push(quote!(resources: Resources::new(#priority)));
+ }
+
+ if ctxt.uses_schedule(app) {
+ let doc = "Tasks that can be `schedule`-d from this context";
+ if ctxt.is_init() {
+ items.push(quote!(
+ #[doc = #doc]
+ #[derive(Clone, Copy)]
+ pub struct Schedule {
+ _not_send: core::marker::PhantomData<*mut ()>,
+ }
+ ));
+
+ fields.push(quote!(
+ #[doc = #doc]
+ pub schedule: Schedule
+ ));
+
+ values.push(quote!(
+ schedule: Schedule { _not_send: core::marker::PhantomData }
+ ));
+ } else {
+ lt = Some(quote!('a));
+
+ items.push(quote!(
+ #[doc = #doc]
+ #[derive(Clone, Copy)]
+ pub struct Schedule<'a> {
+ priority: &'a rtic::export::Priority,
+ }
+
+ impl<'a> Schedule<'a> {
+ #[doc(hidden)]
+ #[inline(always)]
+ pub unsafe fn priority(&self) -> &rtic::export::Priority {
+ &self.priority
+ }
+ }
+ ));
+
+ fields.push(quote!(
+ #[doc = #doc]
+ pub schedule: Schedule<'a>
+ ));
+
+ values.push(quote!(
+ schedule: Schedule { priority }
+ ));
+ }
+ }
+
+ if ctxt.uses_spawn(app) {
+ let doc = "Tasks that can be `spawn`-ed from this context";
+ if ctxt.is_init() {
+ fields.push(quote!(
+ #[doc = #doc]
+ pub spawn: Spawn
+ ));
+
+ items.push(quote!(
+ #[doc = #doc]
+ #[derive(Clone, Copy)]
+ pub struct Spawn {
+ _not_send: core::marker::PhantomData<*mut ()>,
+ }
+ ));
+
+ values.push(quote!(spawn: Spawn { _not_send: core::marker::PhantomData }));
+ } else {
+ lt = Some(quote!('a));
+
+ fields.push(quote!(
+ #[doc = #doc]
+ pub spawn: Spawn<'a>
+ ));
+
+ let mut instant_method = None;
+ if ctxt.is_idle() {
+ items.push(quote!(
+ #[doc = #doc]
+ #[derive(Clone, Copy)]
+ pub struct Spawn<'a> {
+ priority: &'a rtic::export::Priority,
+ }
+ ));
+
+ values.push(quote!(spawn: Spawn { priority }));
+ } else {
+ let instant_field = if app.uses_schedule() {
+ let m = extra.monotonic();
+
+ needs_instant = true;
+ instant_method = Some(quote!(
+ pub unsafe fn instant(&self) -> <#m as rtic::Monotonic>::Instant {
+ self.instant
+ }
+ ));
+ Some(quote!(instant: <#m as rtic::Monotonic>::Instant,))
+ } else {
+ None
+ };
+
+ items.push(quote!(
+ /// Tasks that can be spawned from this context
+ #[derive(Clone, Copy)]
+ pub struct Spawn<'a> {
+ #instant_field
+ priority: &'a rtic::export::Priority,
+ }
+ ));
+
+ let _instant = if needs_instant {
+ Some(quote!(, instant))
+ } else {
+ None
+ };
+ values.push(quote!(
+ spawn: Spawn { priority #_instant }
+ ));
+ }
+
+ items.push(quote!(
+ impl<'a> Spawn<'a> {
+ #[doc(hidden)]
+ #[inline(always)]
+ pub unsafe fn priority(&self) -> &rtic::export::Priority {
+ self.priority
+ }
+
+ #instant_method
+ }
+ ));
+ }
+ }
+
+ if let Context::Init = ctxt {
+ let init = &app.inits.first().unwrap();
+ let late_resources = util::late_resources_ident(&init.name);
+
+ items.push(quote!(
+ #[doc(inline)]
+ pub use super::#late_resources as LateResources;
+ ));
+ }
+
+ let doc = match ctxt {
+ Context::Idle => "Idle loop",
+ Context::Init => "Initialization function",
+ Context::HardwareTask(_) => "Hardware task",
+ Context::SoftwareTask(_) => "Software task",
+ };
+
+ let core = if ctxt.is_init() {
+ if app.uses_schedule() {
+ Some(quote!(core: rtic::Peripherals,))
+ } else {
+ Some(quote!(core: rtic::export::Peripherals,))
+ }
+ } else {
+ None
+ };
+
+ let priority = if ctxt.is_init() {
+ None
+ } else {
+ Some(quote!(priority: &#lt rtic::export::Priority))
+ };
+
+ let instant = if needs_instant {
+ let m = extra.monotonic();
+
+ Some(quote!(, instant: <#m as rtic::Monotonic>::Instant))
+ } else {
+ None
+ };
+
+ items.push(quote!(
+ /// Execution context
+ pub struct Context<#lt> {
+ #(#fields,)*
+ }
+
+ impl<#lt> Context<#lt> {
+ #[inline(always)]
+ pub unsafe fn new(#core #priority #instant) -> Self {
+ Context {
+ #(#values,)*
+ }
+ }
+ }
+ ));
+
+ if !items.is_empty() {
+ quote!(
+ #[allow(non_snake_case)]
+ #[doc = #doc]
+ pub mod #name {
+ #(#items)*
+ }
+ )
+ } else {
+ quote!()
+ }
+}
diff --git a/macros/src/codegen/post_init.rs b/macros/src/codegen/post_init.rs
new file mode 100644
index 00000000..c35c6976
--- /dev/null
+++ b/macros/src/codegen/post_init.rs
@@ -0,0 +1,31 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtic_syntax::ast::App;
+
+use crate::analyze::Analysis;
+
+/// Generates code that runs after `#[init]` returns
+pub fn codegen(app: &App, analysis: &Analysis) -> Vec<TokenStream2> {
+ let mut stmts = vec![];
+
+ // Initialize late resources
+ if analysis.late_resources.len() > 0 {
+ // BTreeSet wrapped in a vector
+ for name in analysis.late_resources.first().unwrap() {
+ // If it's live
+ let cfgs = app.late_resources[name].cfgs.clone();
+ if analysis.locations.get(name).is_some() {
+ // Need to also include the cfgs
+ stmts.push(quote!(
+ #(#cfgs)*
+ #name.as_mut_ptr().write(late.#name);
+ ));
+ }
+ }
+ }
+
+ // Enable the interrupts -- this completes the `init`-ialization phase
+ stmts.push(quote!(rtic::export::interrupt::enable();));
+
+ stmts
+}
diff --git a/macros/src/codegen/pre_init.rs b/macros/src/codegen/pre_init.rs
new file mode 100644
index 00000000..9c5f35ec
--- /dev/null
+++ b/macros/src/codegen/pre_init.rs
@@ -0,0 +1,109 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtic_syntax::ast::App;
+
+use crate::{analyze::Analysis, check::Extra, codegen::util};
+
+/// Generates code that runs before `#[init]`
+pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec<TokenStream2> {
+ let mut stmts = vec![];
+
+ // Disable interrupts -- `init` must run with interrupts disabled
+ stmts.push(quote!(rtic::export::interrupt::disable();));
+
+ // Populate the FreeQueue
+ for fq in &analysis.free_queues {
+ // Get the task name
+ let name = fq.0;
+ let task = &app.software_tasks[name];
+ let cap = task.args.capacity;
+
+ let fq_ident = util::fq_ident(name);
+
+ stmts.push(quote!(
+ (0..#cap).for_each(|i| #fq_ident.enqueue_unchecked(i));
+ ));
+ }
+
+ stmts.push(quote!(
+ // To set the variable in cortex_m so the peripherals cannot be taken multiple times
+ let mut core: rtic::export::Peripherals = rtic::export::Peripherals::steal().into();
+ ));
+
+ let device = extra.device;
+ let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS);
+
+ // Unmask interrupts and set their priorities
+ for (&priority, name) in analysis
+ .interrupts
+ .iter()
+ .chain(app.hardware_tasks.values().flat_map(|task| {
+ if !util::is_exception(&task.args.binds) {
+ Some((&task.args.priority, &task.args.binds))
+ } else {
+ // We do exceptions in another pass
+ None
+ }
+ }))
+ {
+ // Compile time assert that this priority is supported by the device
+ stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];));
+
+ // NOTE this also checks that the interrupt exists in the `Interrupt` enumeration
+ let interrupt = util::interrupt_ident();
+ stmts.push(quote!(
+ core.NVIC.set_priority(
+ #device::#interrupt::#name,
+ rtic::export::logical2hw(#priority, #nvic_prio_bits),
+ );
+ ));
+
+ // NOTE unmask the interrupt *after* setting its priority: changing the priority of a pended
+ // interrupt is implementation defined
+ stmts.push(quote!(rtic::export::NVIC::unmask(#device::#interrupt::#name);));
+ }
+
+ // Set exception priorities
+ for (name, priority) in app.hardware_tasks.values().filter_map(|task| {
+ if util::is_exception(&task.args.binds) {
+ Some((&task.args.binds, task.args.priority))
+ } else {
+ None
+ }
+ }) {
+ // Compile time assert that this priority is supported by the device
+ stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];));
+
+ stmts.push(quote!(core.SCB.set_priority(
+ rtic::export::SystemHandler::#name,
+ rtic::export::logical2hw(#priority, #nvic_prio_bits),
+ );));
+ }
+
+ // Initialize the SysTick if there exist a TimerQueue
+ if let Some(tq) = analysis.timer_queues.first() {
+ let priority = tq.priority;
+
+ // Compile time assert that this priority is supported by the device
+ stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];));
+
+ stmts.push(quote!(core.SCB.set_priority(
+ rtic::export::SystemHandler::SysTick,
+ rtic::export::logical2hw(#priority, #nvic_prio_bits),
+ );));
+
+ stmts.push(quote!(
+ core.SYST.set_clock_source(rtic::export::SystClkSource::Core);
+ core.SYST.enable_counter();
+ core.DCB.enable_trace();
+ ));
+ }
+
+ // If there's no user `#[idle]` then optimize returning from interrupt handlers
+ if app.idles.is_empty() {
+ // Set SLEEPONEXIT bit to enter sleep mode when returning from ISR
+ stmts.push(quote!(core.SCB.scr.modify(|r| r | 1 << 1);));
+ }
+
+ stmts
+}
diff --git a/macros/src/codegen/resources.rs b/macros/src/codegen/resources.rs
new file mode 100644
index 00000000..38ea5245
--- /dev/null
+++ b/macros/src/codegen/resources.rs
@@ -0,0 +1,122 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtic_syntax::{analyze::Ownership, ast::App};
+
+use crate::{analyze::Analysis, check::Extra, codegen::util};
+
+/// Generates `static [mut]` variables and resource proxies
+pub fn codegen(
+ app: &App,
+ analysis: &Analysis,
+ extra: &Extra,
+) -> (
+ // mod_app -- the `static [mut]` variables behind the proxies
+ Vec<TokenStream2>,
+ // mod_resources -- the `resources` module
+ TokenStream2,
+ // mod_resources_imports -- the `resources` module imports
+ Vec<TokenStream2>,
+) {
+ let mut mod_app = vec![];
+ let mut mod_resources = vec![];
+ let mut mod_resources_imports = vec![];
+
+ for (name, res, expr, _) in app.resources(analysis) {
+ let cfgs = &res.cfgs;
+ let ty = &res.ty;
+
+ {
+ let section = if expr.is_none() {
+ util::link_section_uninit(true)
+ } else {
+ None
+ };
+
+ let (ty, expr) = if let Some(expr) = expr {
+ (quote!(#ty), quote!(#expr))
+ } else {
+ (
+ quote!(core::mem::MaybeUninit<#ty>),
+ quote!(core::mem::MaybeUninit::uninit()),
+ )
+ };
+
+ let attrs = &res.attrs;
+ mod_app.push(quote!(
+ #[allow(non_upper_case_globals)]
+ #(#attrs)*
+ #(#cfgs)*
+ #section
+ static mut #name: #ty = #expr;
+ ));
+ }
+
+ if let Some(Ownership::Contended { ceiling }) = analysis.ownerships.get(name) {
+ mod_resources.push(quote!(
+ #[allow(non_camel_case_types)]
+ #(#cfgs)*
+ pub struct #name<'a> {
+ priority: &'a Priority,
+ }
+
+ #(#cfgs)*
+ impl<'a> #name<'a> {
+ #[inline(always)]
+ pub unsafe fn new(priority: &'a Priority) -> Self {
+ #name { priority }
+ }
+
+ #[inline(always)]
+ pub unsafe fn priority(&self) -> &Priority {
+ self.priority
+ }
+ }
+ ));
+
+ let ptr = if expr.is_none() {
+ quote!(
+ #(#cfgs)*
+ #name.as_mut_ptr()
+ )
+ } else {
+ quote!(
+ #(#cfgs)*
+ &mut #name
+ )
+ };
+
+ mod_resources_imports.push(quote!(
+ #[allow(non_camel_case_types)]
+ #(#cfgs)*
+ use super::resources::#name;
+ ));
+
+ mod_app.push(util::impl_mutex(
+ extra,
+ cfgs,
+ true,
+ name,
+ quote!(#ty),
+ *ceiling,
+ ptr,
+ ));
+ }
+ }
+
+ let mod_resources = if mod_resources.is_empty() {
+ quote!()
+ } else {
+ // Also import the resource module
+ mod_resources_imports.push(quote!(
+ use super::resources;
+ ));
+
+ quote!(mod resources {
+ use rtic::export::Priority;
+
+ #(#mod_resources)*
+ })
+ };
+
+ (mod_app, mod_resources, mod_resources_imports)
+}
diff --git a/macros/src/codegen/resources_struct.rs b/macros/src/codegen/resources_struct.rs
new file mode 100644
index 00000000..92d5b666
--- /dev/null
+++ b/macros/src/codegen/resources_struct.rs
@@ -0,0 +1,177 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtic_syntax::{ast::App, Context};
+
+use crate::{analyze::Analysis, codegen::util};
+
+pub fn codegen(
+ ctxt: Context,
+ priority: u8,
+ needs_lt: &mut bool,
+ app: &App,
+ analysis: &Analysis,
+) -> (TokenStream2, TokenStream2) {
+ let mut lt = None;
+
+ let resources = match ctxt {
+ Context::Init => &app.inits.first().unwrap().args.resources,
+ Context::Idle => &app.idles.first().unwrap().args.resources,
+ Context::HardwareTask(name) => &app.hardware_tasks[name].args.resources,
+ Context::SoftwareTask(name) => &app.software_tasks[name].args.resources,
+ };
+
+ let mut fields = vec![];
+ let mut values = vec![];
+ let mut has_cfgs = false;
+
+ for (name, access) in resources {
+ let (res, expr) = app.resource(name).expect("UNREACHABLE");
+
+ let cfgs = &res.cfgs;
+ has_cfgs |= !cfgs.is_empty();
+
+ let mut_ = if access.is_exclusive() {
+ Some(quote!(mut))
+ } else {
+ None
+ };
+ let ty = &res.ty;
+
+ if ctxt.is_init() {
+ if !analysis.ownerships.contains_key(name) {
+ // Owned by `init`
+ fields.push(quote!(
+ #(#cfgs)*
+ pub #name: &'static #mut_ #ty
+ ));
+
+ values.push(quote!(
+ #(#cfgs)*
+ #name: &#mut_ #name
+ ));
+ } else {
+ // Owned by someone else
+ lt = Some(quote!('a));
+
+ fields.push(quote!(
+ #(#cfgs)*
+ pub #name: &'a mut #ty
+ ));
+
+ values.push(quote!(
+ #(#cfgs)*
+ #name: &mut #name
+ ));
+ }
+ } else {
+ let ownership = &analysis.ownerships[name];
+
+ if ownership.needs_lock(priority) {
+ if mut_.is_none() {
+ lt = Some(quote!('a));
+
+ fields.push(quote!(
+ #(#cfgs)*
+ pub #name: &'a #ty
+ ));
+ } else {
+ // Resource proxy
+ lt = Some(quote!('a));
+
+ fields.push(quote!(
+ #(#cfgs)*
+ pub #name: resources::#name<'a>
+ ));
+
+ values.push(quote!(
+ #(#cfgs)*
+ #name: resources::#name::new(priority)
+
+ ));
+
+ continue;
+ }
+ } else {
+ let lt = if ctxt.runs_once() {
+ quote!('static)
+ } else {
+ lt = Some(quote!('a));
+ quote!('a)
+ };
+
+ if ownership.is_owned() || mut_.is_none() {
+ fields.push(quote!(
+ #(#cfgs)*
+ pub #name: &#lt #mut_ #ty
+ ));
+ } else {
+ fields.push(quote!(
+ #(#cfgs)*
+ pub #name: &#lt mut #ty
+ ));
+ }
+ }
+
+ let is_late = expr.is_none();
+ if is_late {
+ let expr = if mut_.is_some() {
+ quote!(&mut *#name.as_mut_ptr())
+ } else {
+ quote!(&*#name.as_ptr())
+ };
+
+ values.push(quote!(
+ #(#cfgs)*
+ #name: #expr
+ ));
+ } else {
+ values.push(quote!(
+ #(#cfgs)*
+ #name: &#mut_ #name
+ ));
+ }
+ }
+ }
+
+ if lt.is_some() {
+ *needs_lt = true;
+
+ // The struct could end up empty due to `cfg`s leading to an error due to `'a` being unused
+ if has_cfgs {
+ fields.push(quote!(
+ #[doc(hidden)]
+ pub __marker__: core::marker::PhantomData<&'a ()>
+ ));
+
+ values.push(quote!(__marker__: core::marker::PhantomData))
+ }
+ }
+
+ let doc = format!("Resources `{}` has access to", ctxt.ident(app));
+ let ident = util::resources_ident(ctxt, app);
+ let item = quote!(
+ #[allow(non_snake_case)]
+ #[doc = #doc]
+ pub struct #ident<#lt> {
+ #(#fields,)*
+ }
+ );
+
+ let arg = if ctxt.is_init() {
+ None
+ } else {
+ Some(quote!(priority: &#lt rtic::export::Priority))
+ };
+ let constructor = quote!(
+ impl<#lt> #ident<#lt> {
+ #[inline(always)]
+ pub unsafe fn new(#arg) -> Self {
+ #ident {
+ #(#values,)*
+ }
+ }
+ }
+ );
+
+ (item, constructor)
+}
diff --git a/macros/src/codegen/schedule.rs b/macros/src/codegen/schedule.rs
new file mode 100644
index 00000000..5a887496
--- /dev/null
+++ b/macros/src/codegen/schedule.rs
@@ -0,0 +1,90 @@
+use std::collections::HashSet;
+
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtic_syntax::ast::App;
+
+use crate::{
+ check::Extra,
+ codegen::{schedule_body, util},
+};
+
+/// Generates all `${ctxt}::Schedule` methods
+pub fn codegen(app: &App, extra: &Extra) -> Vec<TokenStream2> {
+ let mut items = vec![];
+
+ let mut seen = HashSet::<_>::new();
+ for (scheduler, schedulees) in app.schedule_callers() {
+ let m = extra.monotonic();
+ let instant = quote!(<#m as rtic::Monotonic>::Instant);
+
+ let mut methods = vec![];
+
+ for name in schedulees {
+ let schedulee = &app.software_tasks[name];
+ let cfgs = &schedulee.cfgs;
+ let (args, _, untupled, ty) = util::regroup_inputs(&schedulee.inputs);
+ let args = &args;
+
+ if scheduler.is_init() {
+ // `init` uses a special `schedule` implementation; it doesn't use the
+ // `schedule_${name}` functions which are shared by other contexts
+
+ let body = schedule_body::codegen(scheduler, &name, app);
+
+ methods.push(quote!(
+ #(#cfgs)*
+ pub fn #name(&self, instant: #instant #(,#args)*) -> Result<(), #ty> {
+ #body
+ }
+ ));
+ } else {
+ let schedule = util::schedule_ident(name);
+
+ if !seen.contains(name) {
+ // Generate a `schedule_${name}_S${sender}` function
+ seen.insert(name);
+
+ let body = schedule_body::codegen(scheduler, &name, app);
+
+ items.push(quote!(
+ #(#cfgs)*
+ pub unsafe fn #schedule(
+ priority: &rtic::export::Priority,
+ instant: #instant
+ #(,#args)*
+ ) -> Result<(), #ty> {
+ #body
+ }
+ ));
+ }
+
+ methods.push(quote!(
+ #(#cfgs)*
+ #[inline(always)]
+ pub fn #name(&self, instant: #instant #(,#args)*) -> Result<(), #ty> {
+ unsafe {
+ #schedule(self.priority(), instant #(,#untupled)*)
+ }
+ }
+ ));
+ }
+ }
+
+ let lt = if scheduler.is_init() {
+ None
+ } else {
+ Some(quote!('a))
+ };
+
+ let scheduler = scheduler.ident(app);
+ debug_assert!(!methods.is_empty());
+ items.push(quote!(
+ impl<#lt> #scheduler::Schedule<#lt> {
+ #(#methods)*
+ }
+ ));
+ }
+
+ items
+}
diff --git a/macros/src/codegen/schedule_body.rs b/macros/src/codegen/schedule_body.rs
new file mode 100644
index 00000000..644930d7
--- /dev/null
+++ b/macros/src/codegen/schedule_body.rs
@@ -0,0 +1,59 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtic_syntax::{ast::App, Context};
+use syn::Ident;
+
+use crate::codegen::util;
+
+pub fn codegen(scheduler: Context, name: &Ident, app: &App) -> TokenStream2 {
+ let schedulee = &app.software_tasks[name];
+
+ let fq = util::fq_ident(name);
+ let tq = util::tq_ident();
+ let (dequeue, enqueue) = if scheduler.is_init() {
+ (quote!(#fq.dequeue()), quote!(#tq.enqueue_unchecked(nr);))
+ } else {
+ (
+ quote!((#fq { priority }).lock(|fq| fq.split().1.dequeue())),
+ quote!((#tq { priority }).lock(|tq| tq.enqueue_unchecked(nr));),
+ )
+ };
+
+ let write_instant = if app.uses_schedule() {
+ let instants = util::instants_ident(name);
+
+ Some(quote!(
+ #instants.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(instant);
+ ))
+ } else {
+ None
+ };
+
+ let (_, tupled, _, _) = util::regroup_inputs(&schedulee.inputs);
+ let inputs = util::inputs_ident(name);
+ let t = util::schedule_t_ident();
+ quote!(
+ unsafe {
+ use rtic::Mutex as _;
+
+ let input = #tupled;
+ if let Some(index) = #dequeue {
+ #inputs.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(input);
+
+ #write_instant
+
+ let nr = rtic::export::NotReady {
+ instant,
+ index,
+ task: #t::#name,
+ };
+
+ #enqueue
+
+ Ok(())
+ } else {
+ Err(input)
+ }
+ }
+ )
+}
diff --git a/macros/src/codegen/software_tasks.rs b/macros/src/codegen/software_tasks.rs
new file mode 100644
index 00000000..4ae37e4e
--- /dev/null
+++ b/macros/src/codegen/software_tasks.rs
@@ -0,0 +1,169 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::{format_ident, quote};
+use rtic_syntax::{ast::App, Context};
+
+use crate::{
+ analyze::Analysis,
+ check::Extra,
+ codegen::{locals, module, resources_struct, util},
+};
+
+pub fn codegen(
+ app: &App,
+ analysis: &Analysis,
+ extra: &Extra,
+) -> (
+ // mod_app_software_tasks -- free queues, buffers and `${task}Resources` constructors
+ Vec<TokenStream2>,
+ // root_software_tasks -- items that must be placed in the root of the crate:
+ // - `${task}Locals` structs
+ // - `${task}Resources` structs
+ // - `${task}` modules
+ Vec<TokenStream2>,
+ // user_software_tasks -- the `#[task]` functions written by the user
+ Vec<TokenStream2>,
+ // user_software_tasks_imports -- the imports for `#[task]` functions written by the user
+ Vec<TokenStream2>,
+) {
+ let mut mod_app = vec![];
+ let mut root = vec![];
+ let mut user_tasks = vec![];
+ let mut software_tasks_imports = vec![];
+
+ for (name, task) in &app.software_tasks {
+ let inputs = &task.inputs;
+ let (_, _, _, input_ty) = util::regroup_inputs(inputs);
+
+ let cap = task.args.capacity;
+ let cap_lit = util::capacity_literal(cap);
+ let cap_ty = util::capacity_typenum(cap, true);
+
+ // Create free queues and inputs / instants buffers
+ if let Some(&ceiling) = analysis.free_queues.get(name) {
+ let fq = util::fq_ident(name);
+
+ let (fq_ty, fq_expr, mk_uninit): (_, _, Box<dyn Fn() -> Option<_>>) = {
+ (
+ quote!(rtic::export::SCFQ<#cap_ty>),
+ quote!(rtic::export::Queue(unsafe {
+ rtic::export::iQueue::u8_sc()
+ })),
+ Box::new(|| util::link_section_uninit(true)),
+ )
+ };
+ mod_app.push(quote!(
+ /// Queue version of a free-list that keeps track of empty slots in
+ /// the following buffers
+ static mut #fq: #fq_ty = #fq_expr;
+ ));
+
+ // Generate a resource proxy if needed
+ if let Some(ceiling) = ceiling {
+ mod_app.push(quote!(
+ struct #fq<'a> {
+ priority: &'a rtic::export::Priority,
+ }
+ ));
+
+ mod_app.push(util::impl_mutex(
+ extra,
+ &[],
+ false,
+ &fq,
+ fq_ty,
+ ceiling,
+ quote!(&mut #fq),
+ ));
+ }
+
+ let ref elems = (0..cap)
+ .map(|_| quote!(core::mem::MaybeUninit::uninit()))
+ .collect::<Vec<_>>();
+
+ if app.uses_schedule() {
+ let m = extra.monotonic();
+ let instants = util::instants_ident(name);
+
+ let uninit = mk_uninit();
+ mod_app.push(quote!(
+ #uninit
+ /// Buffer that holds the instants associated to the inputs of a task
+ static mut #instants:
+ [core::mem::MaybeUninit<<#m as rtic::Monotonic>::Instant>; #cap_lit] =
+ [#(#elems,)*];
+ ));
+ }
+
+ let uninit = mk_uninit();
+ let inputs = util::inputs_ident(name);
+ mod_app.push(quote!(
+ #uninit
+ /// Buffer that holds the inputs of a task
+ static mut #inputs: [core::mem::MaybeUninit<#input_ty>; #cap_lit] =
+ [#(#elems,)*];
+ ));
+ }
+
+ // `${task}Resources`
+ let mut needs_lt = false;
+ if !task.args.resources.is_empty() {
+ let (item, constructor) = resources_struct::codegen(
+ Context::SoftwareTask(name),
+ task.args.priority,
+ &mut needs_lt,
+ app,
+ analysis,
+ );
+
+ // Add resources to imports
+ let name_res = format_ident!("{}Resources", name);
+ software_tasks_imports.push(quote!(
+ #[allow(non_snake_case)]
+ use super::#name_res;
+ ));
+
+ root.push(item);
+
+ mod_app.push(constructor);
+ }
+
+ // `${task}Locals`
+ let mut locals_pat = None;
+ if !task.locals.is_empty() {
+ let (struct_, pat) = locals::codegen(Context::SoftwareTask(name), &task.locals, app);
+
+ locals_pat = Some(pat);
+ root.push(struct_);
+ }
+
+ let context = &task.context;
+ let attrs = &task.attrs;
+ let cfgs = &task.cfgs;
+ let stmts = &task.stmts;
+ let locals_pat = locals_pat.iter();
+ user_tasks.push(quote!(
+ #(#attrs)*
+ #(#cfgs)*
+ #[allow(non_snake_case)]
+ pub fn #name(#(#locals_pat,)* #context: #name::Context #(,#inputs)*) {
+ use rtic::Mutex as _;
+
+ #(#stmts)*
+ }
+ ));
+ software_tasks_imports.push(quote!(
+ #(#cfgs)*
+ #[allow(non_snake_case)]
+ use super::#name;
+ ));
+
+ root.push(module::codegen(
+ Context::SoftwareTask(name),
+ needs_lt,
+ app,
+ extra,
+ ));
+ }
+
+ (mod_app, root, user_tasks, software_tasks_imports)
+}
diff --git a/macros/src/codegen/spawn.rs b/macros/src/codegen/spawn.rs
new file mode 100644
index 00000000..da281516
--- /dev/null
+++ b/macros/src/codegen/spawn.rs
@@ -0,0 +1,121 @@
+use std::collections::HashSet;
+
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtic_syntax::ast::App;
+
+use crate::{
+ analyze::Analysis,
+ check::Extra,
+ codegen::{spawn_body, util},
+};
+
+/// Generates all `${ctxt}::Spawn` methods
+pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec<TokenStream2> {
+ let mut items = vec![];
+
+ let mut seen = HashSet::<_>::new();
+ for (spawner, spawnees) in app.spawn_callers() {
+ let mut methods = vec![];
+
+ for name in spawnees {
+ let spawnee = &app.software_tasks[name];
+ let cfgs = &spawnee.cfgs;
+ let (args, _, untupled, ty) = util::regroup_inputs(&spawnee.inputs);
+ let args = &args;
+
+ if spawner.is_init() {
+ // `init` uses a special spawn implementation; it doesn't use the `spawn_${name}`
+ // functions which are shared by other contexts
+
+ let body = spawn_body::codegen(spawner, &name, app, analysis, extra);
+
+ let let_instant = if app.uses_schedule() {
+ let m = extra.monotonic();
+
+ Some(quote!(let instant = unsafe { <#m as rtic::Monotonic>::zero() };))
+ } else {
+ None
+ };
+
+ methods.push(quote!(
+ #(#cfgs)*
+ pub fn #name(&self #(,#args)*) -> Result<(), #ty> {
+ #let_instant
+ #body
+ }
+ ));
+ } else {
+ let spawn = util::spawn_ident(name);
+
+ if !seen.contains(name) {
+ // Generate a `spawn_${name}_S${sender}` function
+ seen.insert(name);
+
+ let instant = if app.uses_schedule() {
+ let m = extra.monotonic();
+
+ Some(quote!(, instant: <#m as rtic::Monotonic>::Instant))
+ } else {
+ None
+ };
+
+ let body = spawn_body::codegen(spawner, &name, app, analysis, extra);
+
+ items.push(quote!(
+ #(#cfgs)*
+ unsafe fn #spawn(
+ priority: &rtic::export::Priority
+ #instant
+ #(,#args)*
+ ) -> Result<(), #ty> {
+ #body
+ }
+ ));
+ }
+
+ let (let_instant, instant) = if app.uses_schedule() {
+ let m = extra.monotonic();
+
+ (
+ Some(if spawner.is_idle() {
+ quote!(let instant = <#m as rtic::Monotonic>::now();)
+ } else {
+ quote!(let instant = self.instant();)
+ }),
+ Some(quote!(, instant)),
+ )
+ } else {
+ (None, None)
+ };
+
+ methods.push(quote!(
+ #(#cfgs)*
+ #[inline(always)]
+ pub fn #name(&self #(,#args)*) -> Result<(), #ty> {
+ unsafe {
+ #let_instant
+ #spawn(self.priority() #instant #(,#untupled)*)
+ }
+ }
+ ));
+ }
+ }
+
+ let lt = if spawner.is_init() {
+ None
+ } else {
+ Some(quote!('a))
+ };
+
+ let spawner = spawner.ident(app);
+ debug_assert!(!methods.is_empty());
+ items.push(quote!(
+ impl<#lt> #spawner::Spawn<#lt> {
+ #(#methods)*
+ }
+ ));
+ }
+
+ items
+}
diff --git a/macros/src/codegen/spawn_body.rs b/macros/src/codegen/spawn_body.rs
new file mode 100644
index 00000000..4ecd0757
--- /dev/null
+++ b/macros/src/codegen/spawn_body.rs
@@ -0,0 +1,76 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtic_syntax::{ast::App, Context};
+use syn::Ident;
+
+use crate::{analyze::Analysis, check::Extra, codegen::util};
+
+pub fn codegen(
+ spawner: Context,
+ name: &Ident,
+ app: &App,
+ analysis: &Analysis,
+ extra: &Extra,
+) -> TokenStream2 {
+ let spawnee = &app.software_tasks[name];
+ let priority = spawnee.args.priority;
+
+ let write_instant = if app.uses_schedule() {
+ let instants = util::instants_ident(name);
+
+ Some(quote!(
+ #instants.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(instant);
+ ))
+ } else {
+ None
+ };
+
+ let t = util::spawn_t_ident(priority);
+ let fq = util::fq_ident(name);
+ let rq = util::rq_ident(priority);
+ let (dequeue, enqueue) = if spawner.is_init() {
+ (
+ quote!(#fq.dequeue()),
+ quote!(#rq.enqueue_unchecked((#t::#name, index));),
+ )
+ } else {
+ (
+ quote!((#fq { priority }.lock(|fq| fq.split().1.dequeue()))),
+ quote!((#rq { priority }.lock(|rq| {
+ rq.split().0.enqueue_unchecked((#t::#name, index))
+ }));),
+ )
+ };
+
+ let device = extra.device;
+ let enum_ = util::interrupt_ident();
+ let interrupt = &analysis.interrupts.get(&priority);
+ let pend = {
+ quote!(
+ rtic::pend(#device::#enum_::#interrupt);
+ )
+ };
+
+ let (_, tupled, _, _) = util::regroup_inputs(&spawnee.inputs);
+ let inputs = util::inputs_ident(name);
+ quote!(
+ unsafe {
+ use rtic::Mutex as _;
+
+ let input = #tupled;
+ if let Some(index) = #dequeue {
+ #inputs.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(input);
+
+ #write_instant
+
+ #enqueue
+
+ #pend
+
+ Ok(())
+ } else {
+ Err(input)
+ }
+ }
+ )
+}
diff --git a/macros/src/codegen/timer_queue.rs b/macros/src/codegen/timer_queue.rs
new file mode 100644
index 00000000..030158e2
--- /dev/null
+++ b/macros/src/codegen/timer_queue.rs
@@ -0,0 +1,137 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtic_syntax::ast::App;
+
+use crate::{analyze::Analysis, check::Extra, codegen::util};
+
+/// Generates timer queues and timer queue handlers
+pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec<TokenStream2> {
+ let mut items = vec![];
+
+ if let Some(timer_queue) = &analysis.timer_queues.first() {
+ let t = util::schedule_t_ident();
+
+ // Enumeration of `schedule`-able tasks
+ {
+ let variants = timer_queue
+ .tasks
+ .iter()
+ .map(|name| {
+ let cfgs = &app.software_tasks[name].cfgs;
+
+ quote!(
+ #(#cfgs)*
+ #name
+ )
+ })
+ .collect::<Vec<_>>();
+
+ let doc = format!("Tasks that can be scheduled");
+ items.push(quote!(
+ #[doc = #doc]
+ #[allow(non_camel_case_types)]
+ #[derive(Clone, Copy)]
+ enum #t {
+ #(#variants,)*
+ }
+ ));
+ }
+
+ let tq = util::tq_ident();
+
+ // Static variable and resource proxy
+ {
+ let doc = format!("Timer queue");
+ let m = extra.monotonic();
+ let n = util::capacity_typenum(timer_queue.capacity, false);
+ let tq_ty = quote!(rtic::export::TimerQueue<#m, #t, #n>);
+
+ items.push(quote!(
+ #[doc = #doc]
+ static mut #tq: #tq_ty = rtic::export::TimerQueue(
+ rtic::export::BinaryHeap(
+ rtic::export::iBinaryHeap::new()
+ )
+ );
+
+ struct #tq<'a> {
+ priority: &'a rtic::export::Priority,
+ }
+ ));
+
+ items.push(util::impl_mutex(
+ extra,
+ &[],
+ false,
+ &tq,
+ tq_ty,
+ timer_queue.ceiling,
+ quote!(&mut #tq),
+ ));
+ }
+
+ // Timer queue handler
+ {
+ let device = extra.device;
+ let arms = timer_queue
+ .tasks
+ .iter()
+ .map(|name| {
+ let task = &app.software_tasks[name];
+
+ let cfgs = &task.cfgs;
+ let priority = task.args.priority;
+ let rq = util::rq_ident(priority);
+ let rqt = util::spawn_t_ident(priority);
+ let enum_ = util::interrupt_ident();
+ let interrupt = &analysis.interrupts.get(&priority);
+
+ let pend = {
+ quote!(
+ rtic::pend(#device::#enum_::#interrupt);
+ )
+ };
+
+ quote!(
+ #(#cfgs)*
+ #t::#name => {
+ (#rq { priority: &rtic::export::Priority::new(PRIORITY) }).lock(|rq| {
+ rq.split().0.enqueue_unchecked((#rqt::#name, index))
+ });
+
+ #pend
+ }
+ )
+ })
+ .collect::<Vec<_>>();
+
+ let priority = timer_queue.priority;
+ let sys_tick = util::suffixed("SysTick");
+ items.push(quote!(
+ #[no_mangle]
+ unsafe fn #sys_tick() {
+ use rtic::Mutex as _;
+
+ /// The priority of this handler
+ const PRIORITY: u8 = #priority;
+
+ rtic::export::run(PRIORITY, || {
+ while let Some((task, index)) = (#tq {
+ // NOTE dynamic priority is always the static priority at this point
+ priority: &rtic::export::Priority::new(PRIORITY),
+ })
+ // NOTE `inline(always)` produces faster and smaller code
+ .lock(#[inline(always)]
+ |tq| tq.dequeue())
+ {
+ match task {
+ #(#arms)*
+ }
+ }
+ });
+ }
+ ));
+ }
+ }
+ items
+}
diff --git a/macros/src/codegen/util.rs b/macros/src/codegen/util.rs
new file mode 100644
index 00000000..2f9f3cce
--- /dev/null
+++ b/macros/src/codegen/util.rs
@@ -0,0 +1,247 @@
+use core::sync::atomic::{AtomicUsize, Ordering};
+
+use proc_macro2::{Span, TokenStream as TokenStream2};
+use quote::quote;
+use rtic_syntax::{ast::App, Context};
+use syn::{Attribute, Ident, LitInt, PatType};
+
+use crate::check::Extra;
+
+/// Turns `capacity` into an unsuffixed integer literal
+pub fn capacity_literal(capacity: u8) -> LitInt {
+ LitInt::new(&capacity.to_string(), Span::call_site())
+}
+
+/// Turns `capacity` into a type-level (`typenum`) integer
+pub fn capacity_typenum(capacity: u8, round_up_to_power_of_two: bool) -> TokenStream2 {
+ let capacity = if round_up_to_power_of_two {
+ capacity.checked_next_power_of_two().expect("UNREACHABLE")
+ } else {
+ capacity
+ };
+
+ let ident = Ident::new(&format!("U{}", capacity), Span::call_site());
+
+ quote!(rtic::export::consts::#ident)
+}
+
+/// Identifier for the free queue
+pub fn fq_ident(task: &Ident) -> Ident {
+ Ident::new(&format!("{}_FQ", task.to_string()), Span::call_site())
+}
+
+/// Generates a `Mutex` implementation
+pub fn impl_mutex(
+ extra: &Extra,
+ cfgs: &[Attribute],
+ resources_prefix: bool,
+ name: &Ident,
+ ty: TokenStream2,
+ ceiling: u8,
+ ptr: TokenStream2,
+) -> TokenStream2 {
+ let (path, priority) = if resources_prefix {
+ (quote!(resources::#name), quote!(self.priority()))
+ } else {
+ (quote!(#name), quote!(self.priority))
+ };
+
+ let device = extra.device;
+ quote!(
+ #(#cfgs)*
+ impl<'a> rtic::Mutex for #path<'a> {
+ type T = #ty;
+
+ #[inline(always)]
+ fn lock<R>(&mut self, f: impl FnOnce(&mut #ty) -> R) -> R {
+ /// Priority ceiling
+ const CEILING: u8 = #ceiling;
+
+ unsafe {
+ rtic::export::lock(
+ #ptr,
+ #priority,
+ CEILING,
+ #device::NVIC_PRIO_BITS,
+ f,
+ )
+ }
+ }
+ }
+ )
+}
+
+/// Generates an identifier for the `INPUTS` buffer (`spawn` & `schedule` API)
+pub fn inputs_ident(task: &Ident) -> Ident {
+ Ident::new(&format!("{}_INPUTS", task), Span::call_site())
+}
+
+/// Generates an identifier for the `INSTANTS` buffer (`schedule` API)
+pub fn instants_ident(task: &Ident) -> Ident {
+ Ident::new(&format!("{}_INSTANTS", task), Span::call_site())
+}
+
+pub fn interrupt_ident() -> Ident {
+ let span = Span::call_site();
+ Ident::new("Interrupt", span)
+}
+
+/// Whether `name` is an exception with configurable priority
+pub fn is_exception(name: &Ident) -> bool {
+ let s = name.to_string();
+
+ match &*s {
+ "MemoryManagement" | "BusFault" | "UsageFault" | "SecureFault" | "SVCall"
+ | "DebugMonitor" | "PendSV" | "SysTick" => true,
+
+ _ => false,
+ }
+}
+
+/// Generates a pre-reexport identifier for the "late resources" struct
+pub fn late_resources_ident(init: &Ident) -> Ident {
+ Ident::new(
+ &format!("{}LateResources", init.to_string()),
+ Span::call_site(),
+ )
+}
+
+fn link_section_index() -> usize {
+ static INDEX: AtomicUsize = AtomicUsize::new(0);
+
+ INDEX.fetch_add(1, Ordering::Relaxed)
+}
+
+// NOTE `None` means in shared memory
+pub fn link_section_uninit(empty_expr: bool) -> Option<TokenStream2> {
+ let section = if empty_expr {
+ let index = link_section_index();
+ format!(".uninit.rtic{}", index)
+ } else {
+ format!(".uninit.rtic{}", link_section_index())
+ };
+
+ Some(quote!(#[link_section = #section]))
+}
+
+/// Generates a pre-reexport identifier for the "locals" struct
+pub fn locals_ident(ctxt: Context, app: &App) -> Ident {
+ let mut s = match ctxt {
+ Context::Init => app.inits.first().unwrap().name.to_string(),
+ Context::Idle => app.idles.first().unwrap().name.to_string(),
+ Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(),
+ };
+
+ s.push_str("Locals");
+
+ Ident::new(&s, Span::call_site())
+}
+
+// Regroups the inputs of a task
+//
+// `inputs` could be &[`input: Foo`] OR &[`mut x: i32`, `ref y: i64`]
+pub fn regroup_inputs(
+ inputs: &[PatType],
+) -> (
+ // args e.g. &[`_0`], &[`_0: i32`, `_1: i64`]
+ Vec<TokenStream2>,
+ // tupled e.g. `_0`, `(_0, _1)`
+ TokenStream2,
+ // untupled e.g. &[`_0`], &[`_0`, `_1`]
+ Vec<TokenStream2>,
+ // ty e.g. `Foo`, `(i32, i64)`
+ TokenStream2,
+) {
+ if inputs.len() == 1 {
+ let ty = &inputs[0].ty;
+
+ (
+ vec![quote!(_0: #ty)],
+ quote!(_0),
+ vec![quote!(_0)],
+ quote!(#ty),
+ )
+ } else {
+ let mut args = vec![];
+ let mut pats = vec![];
+ let mut tys = vec![];
+
+ for (i, input) in inputs.iter().enumerate() {
+ let i = Ident::new(&format!("_{}", i), Span::call_site());
+ let ty = &input.ty;
+
+ args.push(quote!(#i: #ty));
+
+ pats.push(quote!(#i));
+
+ tys.push(quote!(#ty));
+ }
+
+ let tupled = {
+ let pats = pats.clone();
+ quote!((#(#pats,)*))
+ };
+ let ty = quote!((#(#tys,)*));
+ (args, tupled, pats, ty)
+ }
+}
+
+/// Generates a pre-reexport identifier for the "resources" struct
+pub fn resources_ident(ctxt: Context, app: &App) -> Ident {
+ let mut s = match ctxt {
+ Context::Init => app.inits.first().unwrap().name.to_string(),
+ Context::Idle => app.idles.first().unwrap().name.to_string(),
+ Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(),
+ };
+
+ s.push_str("Resources");
+
+ Ident::new(&s, Span::call_site())
+}
+
+/// Generates an identifier for a ready queue
+///
+/// There may be several task dispatchers, one for each priority level.
+/// The ready queues are SPSC queues
+pub fn rq_ident(priority: u8) -> Ident {
+ Ident::new(&format!("P{}_RQ", priority), Span::call_site())
+}
+
+/// Generates an identifier for a "schedule" function
+///
+/// The methods of the `Schedule` structs invoke these functions.
+pub fn schedule_ident(name: &Ident) -> Ident {
+ Ident::new(&format!("schedule_{}", name.to_string()), Span::call_site())
+}
+
+/// Generates an identifier for the `enum` of `schedule`-able tasks
+pub fn schedule_t_ident() -> Ident {
+ Ident::new(&format!("T"), Span::call_site())
+}
+
+/// Generates an identifier for a "spawn" function
+///
+/// The methods of the `Spawn` structs invoke these functions.
+pub fn spawn_ident(name: &Ident) -> Ident {
+ Ident::new(&format!("spawn_{}", name.to_string()), Span::call_site())
+}
+
+/// Generates an identifier for the `enum` of `spawn`-able tasks
+///
+/// This identifier needs the same structure as the `RQ` identifier because there's one ready queue
+/// for each of these `T` enums
+pub fn spawn_t_ident(priority: u8) -> Ident {
+ Ident::new(&format!("P{}_T", priority), Span::call_site())
+}
+
+pub fn suffixed(name: &str) -> Ident {
+ let span = Span::call_site();
+ Ident::new(name, span)
+}
+
+/// Generates an identifier for a timer queue
+///
+/// At most there is one timer queue
+pub fn tq_ident() -> Ident {
+ Ident::new(&format!("TQ"), Span::call_site())
+}