aboutsummaryrefslogtreecommitdiff
path: root/macros/src/codegen
diff options
context:
space:
mode:
authorGravatar Jorge Aparicio <jorge@japaric.io> 2019-06-13 23:56:59 +0200
committerGravatar Jorge Aparicio <jorge@japaric.io> 2019-06-13 23:56:59 +0200
commit81275bfa4f41e2066770087f3a33cad4227eab41 (patch)
treec779a68e7cecf4c2613c7593376f980cea5dbc05 /macros/src/codegen
parentfafeeb27270ef24fc3852711c6032f65aa7dbcc0 (diff)
downloadrtic-81275bfa4f41e2066770087f3a33cad4227eab41.tar.gz
rtic-81275bfa4f41e2066770087f3a33cad4227eab41.tar.zst
rtic-81275bfa4f41e2066770087f3a33cad4227eab41.zip
rtfm-syntax refactor + heterogeneous multi-core support
Diffstat (limited to 'macros/src/codegen')
-rw-r--r--macros/src/codegen/assertions.rs26
-rw-r--r--macros/src/codegen/dispatchers.rs178
-rw-r--r--macros/src/codegen/hardware_tasks.rs121
-rw-r--r--macros/src/codegen/idle.rs92
-rw-r--r--macros/src/codegen/init.rs112
-rw-r--r--macros/src/codegen/locals.rs94
-rw-r--r--macros/src/codegen/module.rs328
-rw-r--r--macros/src/codegen/post_init.rs139
-rw-r--r--macros/src/codegen/pre_init.rs150
-rw-r--r--macros/src/codegen/resources.rs115
-rw-r--r--macros/src/codegen/resources_struct.rs178
-rw-r--r--macros/src/codegen/schedule.rs95
-rw-r--r--macros/src/codegen/schedule_body.rs61
-rw-r--r--macros/src/codegen/software_tasks.rs169
-rw-r--r--macros/src/codegen/spawn.rs127
-rw-r--r--macros/src/codegen/spawn_body.rs81
-rw-r--r--macros/src/codegen/timer_queue.rs147
-rw-r--r--macros/src/codegen/util.rs253
18 files changed, 2466 insertions, 0 deletions
diff --git a/macros/src/codegen/assertions.rs b/macros/src/codegen/assertions.rs
new file mode 100644
index 00000000..95268a2c
--- /dev/null
+++ b/macros/src/codegen/assertions.rs
@@ -0,0 +1,26 @@
+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(core: u8, analysis: &Analysis) -> Vec<TokenStream2> {
+ let mut stmts = vec![];
+
+ // we don't generate *all* assertions on all cores because the user could conditionally import a
+ // type only on some core (e.g. `#[cfg(core = "0")] use some::Type;`)
+
+ if let Some(types) = analysis.send_types.get(&core) {
+ for ty in types {
+ stmts.push(quote!(rtfm::export::assert_send::<#ty>();));
+ }
+ }
+
+ if let Some(types) = analysis.sync_types.get(&core) {
+ for ty in types {
+ stmts.push(quote!(rtfm::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..65d25c78
--- /dev/null
+++ b/macros/src/codegen/dispatchers.rs
@@ -0,0 +1,178 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtfm_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![];
+
+ for (&receiver, dispatchers) in &analysis.channels {
+ let interrupts = &analysis.interrupts[&receiver];
+
+ for (&level, channels) in dispatchers {
+ let mut stmts = vec![];
+
+ for (&sender, channel) in channels {
+ let cfg_sender = util::cfg_core(sender, app.args.cores);
+
+ let variants = channel
+ .tasks
+ .iter()
+ .map(|name| {
+ let cfgs = &app.software_tasks[name].cfgs;
+
+ quote!(
+ #(#cfgs)*
+ #name
+ )
+ })
+ .collect::<Vec<_>>();
+
+ let doc = format!(
+ "Software tasks spawned from core #{} to be dispatched at priority level {} by core #{}",
+ sender, level, receiver,
+ );
+ let t = util::spawn_t_ident(receiver, level, sender);
+ 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(receiver, level, sender);
+ let (rq_attr, rq_ty, rq_expr) = if sender == receiver {
+ (
+ cfg_sender.clone(),
+ quote!(rtfm::export::SCRQ<#t, #n>),
+ quote!(rtfm::export::Queue(unsafe {
+ rtfm::export::iQueue::u8_sc()
+ })),
+ )
+ } else {
+ (
+ Some(quote!(#[rtfm::export::shared])),
+ quote!(rtfm::export::MCRQ<#t, #n>),
+ quote!(rtfm::export::Queue(rtfm::export::iQueue::u8())),
+ )
+ };
+
+ let doc = format!(
+ "Queue of tasks sent by core #{} ready to be dispatched by core #{} at priority level {}",
+ sender,
+ receiver,
+ level
+ );
+ items.push(quote!(
+ #[doc = #doc]
+ #rq_attr
+ static mut #rq: #rq_ty = #rq_expr;
+ ));
+
+ if let Some(ceiling) = channel.ceiling {
+ items.push(quote!(
+ #cfg_sender
+ struct #rq<'a> {
+ priority: &'a rtfm::export::Priority,
+ }
+ ));
+
+ items.push(util::impl_mutex(
+ extra,
+ &[],
+ cfg_sender.as_ref(),
+ 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, sender);
+ let inputs = util::inputs_ident(name, sender);
+ let (_, tupled, pats, _) = util::regroup_inputs(&task.inputs);
+
+ let (let_instant, instant) = if app.uses_schedule(receiver) {
+ let instants = util::instants_ident(name, sender);
+
+ (
+ 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 = &rtfm::export::Priority::new(PRIORITY);
+ #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 used by core #{} to dispatch tasks at priority {}",
+ receiver, level
+ );
+ let cfg_receiver = util::cfg_core(receiver, app.args.cores);
+ let interrupt = &interrupts[&level];
+ items.push(quote!(
+ #[allow(non_snake_case)]
+ #[doc = #doc]
+ #[no_mangle]
+ #cfg_receiver
+ unsafe fn #interrupt() {
+ /// The priority of this interrupt handler
+ const PRIORITY: u8 = #level;
+
+ rtfm::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..e65bad56
--- /dev/null
+++ b/macros/src/codegen/hardware_tasks.rs
@@ -0,0 +1,121 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtfm_syntax::{ast::App, Context};
+
+use crate::{
+ analyze::Analysis,
+ check::Extra,
+ codegen::{locals, module, resources_struct, util},
+};
+
+/// Generate support code for hardware tasks (`#[exception]`s and `#[interrupt]`s)
+pub fn codegen(
+ app: &App,
+ analysis: &Analysis,
+ extra: &Extra,
+) -> (
+ // const_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>,
+) {
+ let mut const_app = vec![];
+ let mut root = vec![];
+ let mut user_tasks = vec![];
+
+ for (name, task) in &app.hardware_tasks {
+ let core = task.args.core;
+ let cfg_core = util::cfg_core(core, app.args.cores);
+
+ let (let_instant, instant) = if app.uses_schedule(core) {
+ let m = extra.monotonic();
+
+ (
+ Some(quote!(let instant = <#m as rtfm::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(name);
+ let priority = task.args.priority;
+
+ const_app.push(quote!(
+ #[allow(non_snake_case)]
+ #[no_mangle]
+ #cfg_core
+ unsafe fn #symbol() {
+ const PRIORITY: u8 = #priority;
+
+ #let_instant
+
+ rtfm::export::run(PRIORITY, || {
+ crate::#name(
+ #locals_new
+ #name::Context::new(&rtfm::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,
+ );
+
+ root.push(item);
+
+ const_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;
+ user_tasks.push(quote!(
+ #(#attrs)*
+ #[allow(non_snake_case)]
+ fn #name(#(#locals_pat,)* #context: #name::Context) {
+ use rtfm::Mutex as _;
+
+ #(#stmts)*
+ }
+ ));
+ }
+
+ (const_app, root, user_tasks)
+}
diff --git a/macros/src/codegen/idle.rs b/macros/src/codegen/idle.rs
new file mode 100644
index 00000000..7af01c91
--- /dev/null
+++ b/macros/src/codegen/idle.rs
@@ -0,0 +1,92 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtfm_syntax::{ast::App, Context};
+
+use crate::{
+ analyze::Analysis,
+ check::Extra,
+ codegen::{locals, module, resources_struct, util},
+};
+
+/// Generates support code for `#[idle]` functions
+pub fn codegen(
+ core: u8,
+ app: &App,
+ analysis: &Analysis,
+ extra: &Extra,
+) -> (
+ // const_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>,
+ // call_idle
+ TokenStream2,
+) {
+ if let Some(idle) = app.idles.get(&core) {
+ let mut needs_lt = false;
+ let mut const_app = None;
+ let mut root_idle = vec![];
+ let mut locals_pat = None;
+ let mut locals_new = None;
+
+ if !idle.args.resources.is_empty() {
+ let (item, constructor) =
+ resources_struct::codegen(Context::Idle(core), 0, &mut needs_lt, app, analysis);
+
+ root_idle.push(item);
+ const_app = Some(constructor);
+ }
+
+ let name = &idle.name;
+ if !idle.locals.is_empty() {
+ let (locals, pat) = locals::codegen(Context::Idle(core), &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(core), needs_lt, app, extra));
+
+ let cfg_core = util::cfg_core(core, app.args.cores);
+ let attrs = &idle.attrs;
+ let context = &idle.context;
+ let stmts = &idle.stmts;
+ let user_idle = Some(quote!(
+ #cfg_core
+ #(#attrs)*
+ #[allow(non_snake_case)]
+ fn #name(#(#locals_pat,)* #context: #name::Context) -> ! {
+ use rtfm::Mutex as _;
+
+ #(#stmts)*
+ }
+ ));
+
+ let call_idle = quote!(#name(
+ #(#locals_new,)*
+ #name::Context::new(&rtfm::export::Priority::new(0))
+ ));
+
+ (
+ const_app,
+ root_idle,
+ user_idle,
+ call_idle,
+ )
+ } else {
+ (
+ None,
+ vec![],
+ None,
+ quote!(loop {
+ rtfm::export::wfi()
+ }),
+ )
+ }
+}
diff --git a/macros/src/codegen/init.rs b/macros/src/codegen/init.rs
new file mode 100644
index 00000000..271be94c
--- /dev/null
+++ b/macros/src/codegen/init.rs
@@ -0,0 +1,112 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtfm_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(
+ core: u8,
+ app: &App,
+ analysis: &Analysis,
+ extra: &Extra,
+) -> (
+ // const_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>,
+ // call_init -- the call to the user `#[init]` if there's one
+ Option<TokenStream2>,
+) {
+ if let Some(init) = app.inits.get(&core) {
+ let cfg_core = util::cfg_core(core, app.args.cores);
+ let mut needs_lt = false;
+ let name = &init.name;
+
+ let mut root_init = vec![];
+
+ let ret = {
+ let late_fields = analysis
+ .late_resources
+ .get(&core)
+ .map(|resources| {
+ resources
+ .iter()
+ .map(|name| {
+ let ty = &app.late_resources[name].ty;
+
+ quote!(pub #name: #ty)
+ })
+ .collect::<Vec<_>>()
+ })
+ .unwrap_or(vec![]);
+
+ if !late_fields.is_empty() {
+ let late_resources = util::late_resources_ident(&name);
+
+ root_init.push(quote!(
+ /// Resources initialized at runtime
+ #cfg_core
+ #[allow(non_snake_case)]
+ pub struct #late_resources {
+ #(#late_fields),*
+ }
+ ));
+
+ Some(quote!(-> #name::LateResources))
+ } else {
+ None
+ }
+ };
+
+ let mut locals_pat = None;
+ let mut locals_new = None;
+ if !init.locals.is_empty() {
+ let (struct_, pat) = locals::codegen(Context::Init(core), &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 user_init = Some(quote!(
+ #(#attrs)*
+ #cfg_core
+ #[allow(non_snake_case)]
+ fn #name(#(#locals_pat,)* #context: #name::Context) #ret {
+ #(#stmts)*
+ }
+ ));
+
+ let mut const_app = None;
+ if !init.args.resources.is_empty() {
+ let (item, constructor) =
+ resources_struct::codegen(Context::Init(core), 0, &mut needs_lt, app, analysis);
+
+ root_init.push(item);
+ const_app = Some(constructor);
+ }
+
+ let call_init =
+ Some(quote!(let late = #name(#(#locals_new,)* #name::Context::new(core.into()));));
+
+ root_init.push(module::codegen(Context::Init(core), needs_lt, app, extra));
+
+ (const_app, root_init, user_init, call_init)
+ } else {
+ (None, vec![], None, None)
+ }
+}
diff --git a/macros/src/codegen/locals.rs b/macros/src/codegen/locals.rs
new file mode 100644
index 00000000..96635637
--- /dev/null
+++ b/macros/src/codegen/locals.rs
@@ -0,0 +1,94 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtfm_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..5f077a22
--- /dev/null
+++ b/macros/src/codegen/module.rs
@@ -0,0 +1,328 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtfm_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 core = ctxt.core(app);
+ let mut needs_instant = false;
+ let mut lt = None;
+ match ctxt {
+ Context::Init(core) => {
+ if app.uses_schedule(core) {
+ let m = extra.monotonic();
+
+ fields.push(quote!(
+ /// System start time = `Instant(0 /* cycles */)`
+ pub start: <#m as rtfm::Monotonic>::Instant
+ ));
+
+ values.push(quote!(start: <#m as rtfm::Monotonic>::zero()));
+
+ fields.push(quote!(
+ /// Core (Cortex-M) peripherals minus the SysTick
+ pub core: rtfm::Peripherals
+ ));
+ } else {
+ fields.push(quote!(
+ /// Core (Cortex-M) peripherals
+ pub core: rtfm::export::Peripherals
+ ));
+ }
+
+ if extra.peripherals == Some(core) {
+ let device = extra.device;
+
+ fields.push(quote!(
+ /// Device peripherals
+ pub device: #device::Peripherals
+ ));
+
+ values.push(quote!(device: #device::Peripherals::steal()));
+ }
+
+ values.push(quote!(core));
+ }
+
+ Context::Idle(..) => {}
+
+ Context::HardwareTask(..) => {
+ if app.uses_schedule(core) {
+ let m = extra.monotonic();
+
+ fields.push(quote!(
+ /// Time at which this handler started executing
+ pub start: <#m as rtfm::Monotonic>::Instant
+ ));
+
+ values.push(quote!(start: instant));
+
+ needs_instant = true;
+ }
+ }
+
+ Context::SoftwareTask(..) => {
+ if app.uses_schedule(core) {
+ let m = extra.monotonic();
+
+ fields.push(quote!(
+ /// The time at which this task was scheduled to run
+ pub scheduled: <#m as rtfm::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 rtfm::export::Priority,
+ }
+
+ impl<'a> Schedule<'a> {
+ #[doc(hidden)]
+ #[inline(always)]
+ pub unsafe fn priority(&self) -> &rtfm::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 rtfm::export::Priority,
+ }
+ ));
+
+ values.push(quote!(spawn: Spawn { priority }));
+ } else {
+ let instant_field = if app.uses_schedule(core) {
+ let m = extra.monotonic();
+
+ needs_instant = true;
+ instant_method = Some(quote!(
+ pub unsafe fn instant(&self) -> <#m as rtfm::Monotonic>::Instant {
+ self.instant
+ }
+ ));
+ Some(quote!(instant: <#m as rtfm::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 rtfm::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) -> &rtfm::export::Priority {
+ self.priority
+ }
+
+ #instant_method
+ }
+ ));
+ }
+ }
+
+ if let Context::Init(core) = ctxt {
+ let init = &app.inits[&core];
+ if init.returns_late_resources {
+ 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(core) {
+ Some(quote!(core: rtfm::Peripherals,))
+ } else {
+ Some(quote!(core: rtfm::export::Peripherals,))
+ }
+ } else {
+ None
+ };
+
+ let priority = if ctxt.is_init() {
+ None
+ } else {
+ Some(quote!(priority: &#lt rtfm::export::Priority))
+ };
+
+ let instant = if needs_instant {
+ let m = extra.monotonic();
+
+ Some(quote!(, instant: <#m as rtfm::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() {
+ let cfg_core = util::cfg_core(ctxt.core(app), app.args.cores);
+
+ quote!(
+ #[allow(non_snake_case)]
+ #[doc = #doc]
+ #cfg_core
+ 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..f492d31d
--- /dev/null
+++ b/macros/src/codegen/post_init.rs
@@ -0,0 +1,139 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+
+use crate::{analyze::Analysis, check::Extra, codegen::util};
+
+/// Generates code that runs after `#[init]` returns
+pub fn codegen(
+ core: u8,
+ analysis: &Analysis,
+ extra: &Extra,
+) -> (Vec<TokenStream2>, Vec<TokenStream2>) {
+ let mut const_app = vec![];
+ let mut stmts = vec![];
+
+ // initialize late resources
+ if let Some(late_resources) = analysis.late_resources.get(&core) {
+ for name in late_resources {
+ // if it's live
+ if analysis.locations.get(name).is_some() {
+ stmts.push(quote!(#name.as_mut_ptr().write(late.#name);));
+ }
+ }
+ }
+
+ if analysis.timer_queues.is_empty() {
+ // cross-initialization barriers -- notify *other* cores that their resources have been
+ // initialized
+ if analysis.initialization_barriers.contains_key(&core) {
+ let ib = util::init_barrier(core);
+
+ const_app.push(quote!(
+ #[rtfm::export::shared]
+ static #ib: rtfm::export::Barrier = rtfm::export::Barrier::new();
+ ));
+
+ stmts.push(quote!(
+ #ib.release();
+ ));
+ }
+
+ // then wait until the other cores have initialized *our* resources
+ for (&initializer, users) in &analysis.initialization_barriers {
+ if users.contains(&core) {
+ let ib = util::init_barrier(initializer);
+
+ stmts.push(quote!(
+ #ib.wait();
+ ));
+ }
+ }
+
+ // cross-spawn barriers: wait until other cores are ready to receive messages
+ for (&receiver, senders) in &analysis.spawn_barriers {
+ if senders.get(&core) == Some(&false) {
+ let sb = util::spawn_barrier(receiver);
+
+ stmts.push(quote!(
+ #sb.wait();
+ ));
+ }
+ }
+ } else {
+ // if the `schedule` API is used then we'll synchronize all cores to leave the
+ // `init`-ialization phase at the same time. In this case the rendezvous barrier makes the
+ // cross-initialization and spawn barriers unnecessary
+
+ let m = extra.monotonic();
+
+ if analysis.timer_queues.len() == 1 {
+ // reset the monotonic timer / counter
+ stmts.push(quote!(
+ <#m as rtfm::Monotonic>::reset();
+ ));
+ } else {
+ // in the multi-core case we need a rendezvous (RV) barrier between *all* the cores that
+ // use the `schedule` API; otherwise one of the cores could observe the before-reset
+ // value of the monotonic counter
+ // (this may be easier to implement with `AtomicU8.fetch_sub` but that API is not
+ // available on ARMv6-M)
+
+ // this core will reset the monotonic counter
+ const FIRST: u8 = 0;
+
+ if core == FIRST {
+ for &i in analysis.timer_queues.keys() {
+ let rv = util::rendezvous_ident(i);
+
+ const_app.push(quote!(
+ #[rtfm::export::shared]
+ static #rv: rtfm::export::Barrier = rtfm::export::Barrier::new();
+ ));
+
+ // wait until all the other cores have reached the RV point
+ if i != FIRST {
+ stmts.push(quote!(
+ #rv.wait();
+ ));
+ }
+ }
+
+ let rv = util::rendezvous_ident(core);
+ stmts.push(quote!(
+ // the compiler fences are used to prevent `reset` from being re-ordering wrt to
+ // the atomic operations -- we don't know if `reset` contains load or store
+ // operations
+
+ core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
+
+ // reset the counter
+ <#m as rtfm::Monotonic>::reset();
+
+ core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
+
+ // now unblock all the other cores
+ #rv.release();
+ ));
+ } else {
+ let rv = util::rendezvous_ident(core);
+
+ // let the first core know that we have reached the RV point
+ stmts.push(quote!(
+ #rv.release();
+ ));
+
+ let rv = util::rendezvous_ident(FIRST);
+
+ // wait until the first core has reset the monotonic timer
+ stmts.push(quote!(
+ #rv.wait();
+ ));
+ }
+ }
+ }
+
+ // enable the interrupts -- this completes the `init`-ialization phase
+ stmts.push(quote!(rtfm::export::interrupt::enable();));
+
+ (const_app, stmts)
+}
diff --git a/macros/src/codegen/pre_init.rs b/macros/src/codegen/pre_init.rs
new file mode 100644
index 00000000..3ba17dcf
--- /dev/null
+++ b/macros/src/codegen/pre_init.rs
@@ -0,0 +1,150 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtfm_syntax::ast::{App, HardwareTaskKind};
+
+use crate::{analyze::Analysis, check::Extra, codegen::util};
+
+/// Generates code that runs before `#[init]`
+pub fn codegen(
+ core: u8,
+ app: &App,
+ analysis: &Analysis,
+ extra: &Extra,
+) -> (
+ // `const_app_pre_init` -- `static` variables for barriers
+ Vec<TokenStream2>,
+ // `pre_init_stmts`
+ Vec<TokenStream2>,
+) {
+ let mut const_app = vec![];
+ let mut stmts = vec![];
+
+ // disable interrupts -- `init` must run with interrupts disabled
+ stmts.push(quote!(rtfm::export::interrupt::disable();));
+
+ // populate this core `FreeQueue`s
+ for (name, senders) in &analysis.free_queues {
+ let task = &app.software_tasks[name];
+ let cap = task.args.capacity;
+
+ for &sender in senders.keys() {
+ if sender == core {
+ let fq = util::fq_ident(name, sender);
+
+ stmts.push(quote!(
+ (0..#cap).for_each(|i| #fq.enqueue_unchecked(i));
+ ));
+ }
+ }
+ }
+
+ stmts.push(quote!(
+ let mut core = rtfm::export::Peripherals::steal();
+ ));
+
+ 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
+ .get(&core)
+ .iter()
+ .flat_map(|interrupts| *interrupts)
+ .chain(app.hardware_tasks.iter().flat_map(|(name, task)| {
+ if task.kind == HardwareTaskKind::Interrupt {
+ Some((&task.args.priority, task.args.binds(name)))
+ } 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
+ stmts.push(quote!(
+ core.NVIC.set_priority(
+ #device::Interrupt::#name,
+ rtfm::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!(core.NVIC.enable(#device::Interrupt::#name);));
+ }
+
+ // cross-spawn barriers: now that priorities have been set and the interrupts have been unmasked
+ // we are ready to receive messages from *other* cores
+ if analysis.spawn_barriers.contains_key(&core) {
+ let sb = util::spawn_barrier(core);
+
+ const_app.push(quote!(
+ #[rtfm::export::shared]
+ static #sb: rtfm::export::Barrier = rtfm::export::Barrier::new();
+ ));
+
+ // unblock cores that may send us a message
+ stmts.push(quote!(
+ #sb.release();
+ ));
+ }
+
+ // set exception priorities
+ for (name, priority) in app.hardware_tasks.iter().filter_map(|(name, task)| {
+ if task.kind == HardwareTaskKind::Exception {
+ Some((task.args.binds(name), 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(
+ rtfm::export::SystemHandler::#name,
+ rtfm::export::logical2hw(#priority, #nvic_prio_bits),
+ );));
+ }
+
+ // initialize the SysTick
+ if let Some(tq) = analysis.timer_queues.get(&core) {
+ 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(
+ rtfm::export::SystemHandler::SysTick,
+ rtfm::export::logical2hw(#priority, #nvic_prio_bits),
+ );));
+
+ stmts.push(quote!(
+ core.SYST.set_clock_source(rtfm::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.get(&core).is_none() {
+ // Set SLEEPONEXIT bit to enter sleep mode when returning from ISR
+ stmts.push(quote!(core.SCB.scr.modify(|r| r | 1 << 1);));
+ }
+
+ // cross-spawn barriers: wait until other cores are ready to receive messages
+ for (&receiver, senders) in &analysis.spawn_barriers {
+ // only block here if `init` can send messages to `receiver`
+ if senders.get(&core) == Some(&true) {
+ let sb = util::spawn_barrier(receiver);
+
+ stmts.push(quote!(
+ #sb.wait();
+ ));
+ }
+ }
+
+ (const_app, stmts)
+}
diff --git a/macros/src/codegen/resources.rs b/macros/src/codegen/resources.rs
new file mode 100644
index 00000000..2dd10eac
--- /dev/null
+++ b/macros/src/codegen/resources.rs
@@ -0,0 +1,115 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtfm_syntax::{
+ analyze::{Location, 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,
+) -> (
+ // const_app -- the `static [mut]` variables behind the proxies
+ Vec<TokenStream2>,
+ // mod_resources -- the `resources` module
+ TokenStream2,
+) {
+ let mut const_app = vec![];
+ let mut mod_resources = vec![];
+
+ for (name, res, expr, loc) in app.resources(analysis) {
+ let cfgs = &res.cfgs;
+ let ty = &res.ty;
+
+ {
+ let loc_attr = match loc {
+ Location::Owned {
+ core,
+ cross_initialized: false,
+ } => util::cfg_core(*core, app.args.cores),
+
+ // shared `static`s and cross-initialized resources need to be in `.shared` memory
+ _ => Some(quote!(#[rtfm::export::shared])),
+ };
+
+ 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;
+ const_app.push(quote!(
+ #loc_attr
+ #(#attrs)*
+ #(#cfgs)*
+ static mut #name: #ty = #expr;
+ ));
+ }
+
+ // generate a resource proxy if needed
+ if res.mutability.is_some() {
+ if let Some(Ownership::Shared { ceiling }) = analysis.ownerships.get(name) {
+ let cfg_core = util::cfg_core(loc.core().expect("UNREACHABLE"), app.args.cores);
+
+ mod_resources.push(quote!(
+ #(#cfgs)*
+ #cfg_core
+ pub struct #name<'a> {
+ priority: &'a Priority,
+ }
+
+ #(#cfgs)*
+ #cfg_core
+ 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!(#name.as_mut_ptr())
+ } else {
+ quote!(&mut #name)
+ };
+
+ const_app.push(util::impl_mutex(
+ extra,
+ cfgs,
+ cfg_core.as_ref(),
+ true,
+ name,
+ quote!(#ty),
+ *ceiling,
+ ptr,
+ ));
+ }
+ }
+ }
+
+ let mod_resources = if mod_resources.is_empty() {
+ quote!()
+ } else {
+ quote!(mod resources {
+ use rtfm::export::Priority;
+
+ #(#mod_resources)*
+ })
+ };
+
+ (const_app, mod_resources)
+}
diff --git a/macros/src/codegen/resources_struct.rs b/macros/src/codegen/resources_struct.rs
new file mode 100644
index 00000000..0248f199
--- /dev/null
+++ b/macros/src/codegen/resources_struct.rs
@@ -0,0 +1,178 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtfm_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(core) => &app.inits[&core].args.resources,
+ Context::Idle(core) => &app.idles[&core].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 in resources {
+ let (res, expr) = app.resource(name).expect("UNREACHABLE");
+
+ let cfgs = &res.cfgs;
+ has_cfgs |= !cfgs.is_empty();
+
+ let mut_ = res.mutability;
+ 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 core = ctxt.core(app);
+ let cores = app.args.cores;
+ let cfg_core = util::cfg_core(core, cores);
+ let doc = format!("Resources `{}` has access to", ctxt.ident(app));
+ let ident = util::resources_ident(ctxt, app);
+ let item = quote!(
+ #cfg_core
+ #[allow(non_snake_case)]
+ #[doc = #doc]
+ pub struct #ident<#lt> {
+ #(#fields,)*
+ }
+ );
+
+ let arg = if ctxt.is_init() {
+ None
+ } else {
+ Some(quote!(priority: &#lt rtfm::export::Priority))
+ };
+ let constructor = quote!(
+ #cfg_core
+ impl<#lt> #ident<#lt> {
+ #[inline(always)]
+ 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..57f01a2c
--- /dev/null
+++ b/macros/src/codegen/schedule.rs
@@ -0,0 +1,95 @@
+use std::collections::{BTreeMap, HashSet};
+
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtfm_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 = BTreeMap::<u8, HashSet<_>>::new();
+ for (scheduler, schedulees) in app.schedule_callers() {
+ let m = extra.monotonic();
+ let instant = quote!(<#m as rtfm::Monotonic>::Instant);
+
+ let sender = scheduler.core(app);
+ let cfg_sender = util::cfg_core(sender, app.args.cores);
+ let seen = seen.entry(sender).or_default();
+ 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)*
+ fn #name(&self, instant: #instant #(,#args)*) -> Result<(), #ty> {
+ #body
+ }
+ ));
+ } else {
+ let schedule = util::schedule_ident(name, sender);
+
+ 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!(
+ #cfg_sender
+ #(#cfgs)*
+ unsafe fn #schedule(
+ priority: &rtfm::export::Priority,
+ instant: #instant
+ #(,#args)*
+ ) -> Result<(), #ty> {
+ #body
+ }
+ ));
+ }
+
+ methods.push(quote!(
+ #(#cfgs)*
+ #[inline(always)]
+ 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!(
+ #cfg_sender
+ 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..208fd0b7
--- /dev/null
+++ b/macros/src/codegen/schedule_body.rs
@@ -0,0 +1,61 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtfm_syntax::{ast::App, Context};
+use syn::Ident;
+
+use crate::codegen::util;
+
+pub fn codegen(scheduler: Context, name: &Ident, app: &App) -> TokenStream2 {
+ let sender = scheduler.core(app);
+ let schedulee = &app.software_tasks[name];
+ let receiver = schedulee.args.core;
+
+ let fq = util::fq_ident(name, sender);
+ let tq = util::tq_ident(sender);
+ 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(receiver) {
+ let instants = util::instants_ident(name, sender);
+
+ 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, sender);
+ let t = util::schedule_t_ident(sender);
+ quote!(
+ unsafe {
+ use rtfm::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 = rtfm::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..8b2c0cd5
--- /dev/null
+++ b/macros/src/codegen/software_tasks.rs
@@ -0,0 +1,169 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtfm_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,
+) -> (
+ // const_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>,
+) {
+ let mut const_app = vec![];
+ let mut root = vec![];
+ let mut user_tasks = vec![];
+
+ for (name, task) in &app.software_tasks {
+ let receiver = task.args.core;
+
+ 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(free_queues) = analysis.free_queues.get(name) {
+ for (&sender, &ceiling) in free_queues {
+ let cfg_sender = util::cfg_core(sender, app.args.cores);
+ let fq = util::fq_ident(name, sender);
+
+ let (loc, fq_ty, fq_expr) = if receiver == sender {
+ (
+ cfg_sender.clone(),
+ quote!(rtfm::export::SCFQ<#cap_ty>),
+ quote!(rtfm::export::Queue(unsafe {
+ rtfm::export::iQueue::u8_sc()
+ })),
+ )
+ } else {
+ (
+ Some(quote!(#[rtfm::export::shared])),
+ quote!(rtfm::export::MCFQ<#cap_ty>),
+ quote!(rtfm::export::Queue(rtfm::export::iQueue::u8())),
+ )
+ };
+ let loc = &loc;
+
+ const_app.push(quote!(
+ /// Queue version of a free-list that keeps track of empty slots in
+ /// the following buffers
+ #loc
+ static mut #fq: #fq_ty = #fq_expr;
+ ));
+
+ // Generate a resource proxy if needed
+ if let Some(ceiling) = ceiling {
+ const_app.push(quote!(
+ #cfg_sender
+ struct #fq<'a> {
+ priority: &'a rtfm::export::Priority,
+ }
+ ));
+
+ const_app.push(util::impl_mutex(
+ extra,
+ &[],
+ cfg_sender.as_ref(),
+ 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(receiver) {
+ let m = extra.monotonic();
+ let instants = util::instants_ident(name, sender);
+
+ const_app.push(quote!(
+ #loc
+ /// Buffer that holds the instants associated to the inputs of a task
+ static mut #instants:
+ [core::mem::MaybeUninit<<#m as rtfm::Monotonic>::Instant>; #cap_lit] =
+ [#(#elems,)*];
+ ));
+ }
+
+ let inputs = util::inputs_ident(name, sender);
+ const_app.push(quote!(
+ #loc
+ /// 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,
+ );
+
+ root.push(item);
+
+ const_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 cfg_receiver = util::cfg_core(receiver, app.args.cores);
+ let context = &task.context;
+ let attrs = &task.attrs;
+ let cfgs = &task.cfgs;
+ let stmts = &task.stmts;
+ user_tasks.push(quote!(
+ #(#attrs)*
+ #(#cfgs)*
+ #cfg_receiver
+ #[allow(non_snake_case)]
+ fn #name(#(#locals_pat,)* #context: #name::Context #(,#inputs)*) {
+ use rtfm::Mutex as _;
+
+ #(#stmts)*
+ }
+ ));
+
+ root.push(module::codegen(
+ Context::SoftwareTask(name),
+ needs_lt,
+ app,
+ extra,
+ ));
+ }
+
+ (const_app, root, user_tasks)
+}
diff --git a/macros/src/codegen/spawn.rs b/macros/src/codegen/spawn.rs
new file mode 100644
index 00000000..1539e277
--- /dev/null
+++ b/macros/src/codegen/spawn.rs
@@ -0,0 +1,127 @@
+use std::collections::{BTreeMap, HashSet};
+
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtfm_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 = BTreeMap::<u8, HashSet<_>>::new();
+ for (spawner, spawnees) in app.spawn_callers() {
+ let sender = spawner.core(app);
+ let cfg_sender = util::cfg_core(sender, app.args.cores);
+ let seen = seen.entry(sender).or_default();
+ let mut methods = vec![];
+
+ for name in spawnees {
+ let spawnee = &app.software_tasks[name];
+ let receiver = spawnee.args.core;
+ 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(receiver) {
+ let m = extra.monotonic();
+
+ Some(quote!(let instant = unsafe { <#m as rtfm::Monotonic>::zero() };))
+ } else {
+ None
+ };
+
+ methods.push(quote!(
+ #(#cfgs)*
+ fn #name(&self #(,#args)*) -> Result<(), #ty> {
+ #let_instant
+ #body
+ }
+ ));
+ } else {
+ let spawn = util::spawn_ident(name, sender);
+
+ if !seen.contains(name) {
+ // generate a `spawn_${name}_S${sender}` function
+ seen.insert(name);
+
+ let instant = if app.uses_schedule(receiver) {
+ let m = extra.monotonic();
+
+ Some(quote!(, instant: <#m as rtfm::Monotonic>::Instant))
+ } else {
+ None
+ };
+
+ let body = spawn_body::codegen(spawner, &name, app, analysis, extra);
+
+ items.push(quote!(
+ #cfg_sender
+ #(#cfgs)*
+ unsafe fn #spawn(
+ priority: &rtfm::export::Priority
+ #instant
+ #(,#args)*
+ ) -> Result<(), #ty> {
+ #body
+ }
+ ));
+ }
+
+ let (let_instant, instant) = if app.uses_schedule(receiver) {
+ let m = extra.monotonic();
+
+ (
+ Some(if spawner.is_idle() {
+ quote!(let instant = <#m as rtfm::Monotonic>::now();)
+ } else {
+ quote!(let instant = self.instant();)
+ }),
+ Some(quote!(, instant)),
+ )
+ } else {
+ (None, None)
+ };
+
+ methods.push(quote!(
+ #(#cfgs)*
+ #[inline(always)]
+ 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!(
+ #cfg_sender
+ 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..83cb5c0a
--- /dev/null
+++ b/macros/src/codegen/spawn_body.rs
@@ -0,0 +1,81 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtfm_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 sender = spawner.core(app);
+ let spawnee = &app.software_tasks[name];
+ let priority = spawnee.args.priority;
+ let receiver = spawnee.args.core;
+
+ let write_instant = if app.uses_schedule(receiver) {
+ let instants = util::instants_ident(name, sender);
+
+ Some(quote!(
+ #instants.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(instant);
+ ))
+ } else {
+ None
+ };
+
+ let t = util::spawn_t_ident(receiver, priority, sender);
+ let fq = util::fq_ident(name, sender);
+ let rq = util::rq_ident(receiver, priority, sender);
+ 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 interrupt = &analysis.interrupts[&receiver][&priority];
+ let pend = if sender != receiver {
+ quote!(
+ #device::xpend(#receiver, #device::Interrupt::#interrupt);
+ )
+ } else {
+ quote!(
+ rtfm::pend(#device::Interrupt::#interrupt);
+ )
+ };
+
+ let (_, tupled, _, _) = util::regroup_inputs(&spawnee.inputs);
+ let inputs = util::inputs_ident(name, sender);
+ quote!(
+ unsafe {
+ use rtfm::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..cb845774
--- /dev/null
+++ b/macros/src/codegen/timer_queue.rs
@@ -0,0 +1,147 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtfm_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![];
+
+ for (&sender, timer_queue) in &analysis.timer_queues {
+ let cfg_sender = util::cfg_core(sender, app.args.cores);
+ let t = util::schedule_t_ident(sender);
+
+ // 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 from core #{}", sender);
+ items.push(quote!(
+ #cfg_sender
+ #[doc = #doc]
+ #[allow(non_camel_case_types)]
+ #[derive(Clone, Copy)]
+ enum #t {
+ #(#variants,)*
+ }
+ ));
+ }
+
+ let tq = util::tq_ident(sender);
+
+ // Static variable and resource proxy
+ {
+ let doc = format!("Core #{} timer queue", sender);
+ let m = extra.monotonic();
+ let n = util::capacity_typenum(timer_queue.capacity, false);
+ let tq_ty = quote!(rtfm::export::TimerQueue<#m, #t, #n>);
+
+ items.push(quote!(
+ #cfg_sender
+ #[doc = #doc]
+ static mut #tq: #tq_ty = rtfm::export::TimerQueue(
+ rtfm::export::BinaryHeap(
+ rtfm::export::iBinaryHeap::new()
+ )
+ );
+
+ #cfg_sender
+ struct #tq<'a> {
+ priority: &'a rtfm::export::Priority,
+ }
+ ));
+
+ items.push(util::impl_mutex(
+ extra,
+ &[],
+ cfg_sender.as_ref(),
+ 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 receiver = task.args.core;
+ let rq = util::rq_ident(receiver, priority, sender);
+ let rqt = util::spawn_t_ident(receiver, priority, sender);
+ let interrupt = &analysis.interrupts[&receiver][&priority];
+
+ let pend = if sender != receiver {
+ quote!(
+ #device::xpend(#receiver, #device::Interrupt::#interrupt);
+ )
+ } else {
+ quote!(
+ rtfm::pend(#device::Interrupt::#interrupt);
+ )
+ };
+
+ quote!(
+ #(#cfgs)*
+ #t::#name => {
+ (#rq { priority: &rtfm::export::Priority::new(PRIORITY) }).lock(|rq| {
+ rq.split().0.enqueue_unchecked((#rqt::#name, index))
+ });
+
+ #pend
+ }
+ )
+ })
+ .collect::<Vec<_>>();
+
+ let priority = timer_queue.priority;
+ items.push(quote!(
+ #cfg_sender
+ #[no_mangle]
+ unsafe fn SysTick() {
+ use rtfm::Mutex as _;
+
+ /// The priority of this handler
+ const PRIORITY: u8 = #priority;
+
+ rtfm::export::run(PRIORITY, || {
+ while let Some((task, index)) = (#tq {
+ // NOTE dynamic priority is always the static priority at this point
+ priority: &rtfm::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..203fcee8
--- /dev/null
+++ b/macros/src/codegen/util.rs
@@ -0,0 +1,253 @@
+use proc_macro2::{Span, TokenStream as TokenStream2};
+use quote::quote;
+use rtfm_syntax::{ast::App, Context, Core};
+use syn::{ArgCaptured, Attribute, Ident, IntSuffix, LitInt};
+
+use crate::check::Extra;
+
+/// Turns `capacity` into an unsuffixed integer literal
+pub fn capacity_literal(capacity: u8) -> LitInt {
+ LitInt::new(u64::from(capacity), IntSuffix::None, 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!(rtfm::export::consts::#ident)
+}
+
+/// Generates a `#[cfg(core = "0")]` attribute if we are in multi-core mode
+pub fn cfg_core(core: Core, cores: u8) -> Option<TokenStream2> {
+ if cores == 1 {
+ None
+ } else {
+ let core = core.to_string();
+ Some(quote!(#[cfg(core = #core)]))
+ }
+}
+
+/// Identifier for the free queue
+///
+/// There may be more than one free queue per task because we need one for each sender core so we
+/// include the sender (e.g. `S0`) in the name
+pub fn fq_ident(task: &Ident, sender: Core) -> Ident {
+ Ident::new(
+ &format!("{}_S{}_FQ", task.to_string(), sender),
+ Span::call_site(),
+ )
+}
+
+/// Generates a `Mutex` implementation
+pub fn impl_mutex(
+ extra: &Extra,
+ cfgs: &[Attribute],
+ cfg_core: Option<&TokenStream2>,
+ 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)*
+ #cfg_core
+ impl<'a> rtfm::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 {
+ rtfm::export::lock(
+ #ptr,
+ #priority,
+ CEILING,
+ #device::NVIC_PRIO_BITS,
+ f,
+ )
+ }
+ }
+ }
+ )
+}
+
+/// Generates an identifier for a cross-initialization barrier
+pub fn init_barrier(initializer: Core) -> Ident {
+ Ident::new(&format!("IB{}", initializer), Span::call_site())
+}
+
+/// Generates an identifier for the `INPUTS` buffer (`spawn` & `schedule` API)
+pub fn inputs_ident(task: &Ident, sender: Core) -> Ident {
+ Ident::new(&format!("{}_S{}_INPUTS", task, sender), Span::call_site())
+}
+
+/// Generates an identifier for the `INSTANTS` buffer (`schedule` API)
+pub fn instants_ident(task: &Ident, sender: Core) -> Ident {
+ Ident::new(&format!("{}_S{}_INSTANTS", task, sender), Span::call_site())
+}
+
+/// 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(),
+ )
+}
+
+/// 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(core) => app.inits[&core].name.to_string(),
+ Context::Idle(core) => app.idles[&core].name.to_string(),
+ Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(),
+ };
+
+ s.push_str("Locals");
+
+ Ident::new(&s, Span::call_site())
+}
+
+/// Generates an identifier for a rendezvous barrier
+pub fn rendezvous_ident(core: Core) -> Ident {
+ Ident::new(&format!("RV{}", core), 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: &[ArgCaptured],
+) -> (
+ // 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(core) => app.inits[&core].name.to_string(),
+ Context::Idle(core) => app.idles[&core].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
+///
+/// Each core may have several task dispatchers, one for each priority level. Each task dispatcher
+/// in turn may use more than one ready queue because the queues are SPSC queues so one is needed
+/// per sender core.
+pub fn rq_ident(receiver: Core, priority: u8, sender: Core) -> Ident {
+ Ident::new(
+ &format!("R{}_P{}_S{}_RQ", receiver, priority, sender),
+ Span::call_site(),
+ )
+}
+
+/// Generates an identifier for a "schedule" function
+///
+/// The methods of the `Schedule` structs invoke these functions. As one task may be `schedule`-ed
+/// by different cores we need one "schedule" function per possible task-sender pair
+pub fn schedule_ident(name: &Ident, sender: Core) -> Ident {
+ Ident::new(
+ &format!("schedule_{}_S{}", name.to_string(), sender),
+ Span::call_site(),
+ )
+}
+
+/// Generates an identifier for the `enum` of `schedule`-able tasks
+pub fn schedule_t_ident(core: Core) -> Ident {
+ Ident::new(&format!("T{}", core), Span::call_site())
+}
+
+/// Generates an identifier for a cross-spawn barrier
+pub fn spawn_barrier(receiver: Core) -> Ident {
+ Ident::new(&format!("SB{}", receiver), Span::call_site())
+}
+
+/// Generates an identifier for a "spawn" function
+///
+/// The methods of the `Spawn` structs invoke these functions. As one task may be `spawn`-ed by
+/// different cores we need one "spawn" function per possible task-sender pair
+pub fn spawn_ident(name: &Ident, sender: Core) -> Ident {
+ Ident::new(
+ &format!("spawn_{}_S{}", name.to_string(), sender),
+ 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(receiver: Core, priority: u8, sender: Core) -> Ident {
+ Ident::new(
+ &format!("R{}_P{}_S{}_T", receiver, priority, sender),
+ Span::call_site(),
+ )
+}
+
+/// Generates an identifier for a timer queue
+///
+/// At most there's one timer queue per core
+pub fn tq_ident(core: Core) -> Ident {
+ Ident::new(&format!("TQ{}", core), Span::call_site())
+}