diff options
Diffstat (limited to 'macros/src/codegen')
-rw-r--r-- | macros/src/codegen/assertions.rs | 33 | ||||
-rw-r--r-- | macros/src/codegen/dispatchers.rs | 189 | ||||
-rw-r--r-- | macros/src/codegen/hardware_tasks.rs | 132 | ||||
-rw-r--r-- | macros/src/codegen/idle.rs | 91 | ||||
-rw-r--r-- | macros/src/codegen/init.rs | 116 | ||||
-rw-r--r-- | macros/src/codegen/locals.rs | 101 | ||||
-rw-r--r-- | macros/src/codegen/module.rs | 328 | ||||
-rw-r--r-- | macros/src/codegen/post_init.rs | 155 | ||||
-rw-r--r-- | macros/src/codegen/pre_init.rs | 159 | ||||
-rw-r--r-- | macros/src/codegen/resources.rs | 125 | ||||
-rw-r--r-- | macros/src/codegen/resources_struct.rs | 182 | ||||
-rw-r--r-- | macros/src/codegen/schedule.rs | 99 | ||||
-rw-r--r-- | macros/src/codegen/schedule_body.rs | 61 | ||||
-rw-r--r-- | macros/src/codegen/software_tasks.rs | 194 | ||||
-rw-r--r-- | macros/src/codegen/spawn.rs | 131 | ||||
-rw-r--r-- | macros/src/codegen/spawn_body.rs | 82 | ||||
-rw-r--r-- | macros/src/codegen/timer_queue.rs | 153 | ||||
-rw-r--r-- | macros/src/codegen/util.rs | 325 |
18 files changed, 2656 insertions, 0 deletions
diff --git a/macros/src/codegen/assertions.rs b/macros/src/codegen/assertions.rs new file mode 100644 index 00000000..4a77352f --- /dev/null +++ b/macros/src/codegen/assertions.rs @@ -0,0 +1,33 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +use crate::{analyze::Analysis, check::Extra}; + +/// Generates compile-time assertions that check that types implement the `Send` / `Sync` traits +pub fn codegen(core: u8, analysis: &Analysis, extra: &Extra) -> 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>();)); + } + } + + // if the `schedule` API is used in more than one core then we need to check that the + // `monotonic` timer can be used in multi-core context + if analysis.timer_queues.len() > 1 && analysis.timer_queues.contains_key(&core) { + let monotonic = extra.monotonic(); + stmts.push(quote!(rtfm::export::assert_multicore::<#monotonic>();)); + } + + stmts +} diff --git a/macros/src/codegen/dispatchers.rs b/macros/src/codegen/dispatchers.rs new file mode 100644 index 00000000..9a9cb102 --- /dev/null +++ b/macros/src/codegen/dispatchers.rs @@ -0,0 +1,189 @@ +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, section) = if sender == receiver { + ( + cfg_sender.clone(), + quote!(rtfm::export::SCRQ<#t, #n>), + quote!(rtfm::export::Queue(unsafe { + rtfm::export::iQueue::u8_sc() + })), + util::link_section("bss", sender), + ) + } else { + let shared = if cfg!(feature = "heterogeneous") { + Some(quote!(#[rtfm::export::shared])) + } else { + None + }; + + ( + shared, + quote!(rtfm::export::MCRQ<#t, #n>), + quote!(rtfm::export::Queue(rtfm::export::iQueue::u8())), + None, + ) + }; + + 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 + #section + 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 section = util::link_section("text", receiver); + let interrupt = util::suffixed(&interrupts[&level].to_string(), receiver); + items.push(quote!( + #[allow(non_snake_case)] + #[doc = #doc] + #[no_mangle] + #cfg_receiver + #section + 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..a9c2a2bd --- /dev/null +++ b/macros/src/codegen/hardware_tasks.rs @@ -0,0 +1,132 @@ +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 = if cfg!(feature = "homogeneous") { + util::suffixed(&task.args.binds.to_string(), core) + } else { + task.args.binds.clone() + }; + let priority = task.args.priority; + + let section = util::link_section("text", core); + const_app.push(quote!( + #[allow(non_snake_case)] + #[no_mangle] + #section + #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, core, app); + + root.push(struct_); + locals_pat = Some(pat); + } + + let attrs = &task.attrs; + let context = &task.context; + let stmts = &task.stmts; + let section = util::link_section("text", core); + // XXX shouldn't this have a cfg_core? + let locals_pat = locals_pat.iter(); + user_tasks.push(quote!( + #(#attrs)* + #[allow(non_snake_case)] + #section + 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..35a72523 --- /dev/null +++ b/macros/src/codegen/idle.rs @@ -0,0 +1,91 @@ +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, core, 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 section = util::link_section("text", core); + let locals_pat = locals_pat.iter(); + let user_idle = Some(quote!( + #(#attrs)* + #[allow(non_snake_case)] + #cfg_core + #section + fn #name(#(#locals_pat,)* #context: #name::Context) -> ! { + use rtfm::Mutex as _; + + #(#stmts)* + } + )); + + let locals_new = locals_new.iter(); + 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..9c8ce31c --- /dev/null +++ b/macros/src/codegen/init.rs @@ -0,0 +1,116 @@ +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, core, 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 section = util::link_section("text", core); + let locals_pat = locals_pat.iter(); + let user_init = Some(quote!( + #(#attrs)* + #cfg_core + #[allow(non_snake_case)] + #section + 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 locals_new = locals_new.iter(); + 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..cbfe05fb --- /dev/null +++ b/macros/src/codegen/locals.rs @@ -0,0 +1,101 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::{ + ast::{App, Local}, + Context, Core, Map, +}; + +use crate::codegen::util; + +pub fn codegen( + ctxt: Context, + locals: &Map<Local>, + core: Core, + 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 section = if local.shared && cfg!(feature = "heterogeneous") { + Some(quote!(#[rtfm::export::shared])) + } else { + util::link_section("data", core) + }; + let expr = &local.expr; + let ty = &local.ty; + fields.push(quote!( + #(#cfgs)* + #name: &#lt mut #ty + )); + items.push(quote!( + #(#cfgs)* + #section + 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..19773e45 --- /dev/null +++ b/macros/src/codegen/post_init.rs @@ -0,0 +1,155 @@ +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 + for (user, initializers) in &analysis.initialization_barriers { + if !initializers.contains(&core) { + continue; + } + + let ib = util::init_barrier(*user); + let shared = if cfg!(feature = "heterogeneous") { + Some(quote!( + #[rtfm::export::shared] + )) + } else { + None + }; + + const_app.push(quote!( + #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 + if analysis.initialization_barriers.contains_key(&core) { + let ib = util::init_barrier(core); + + 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); + let shared = if cfg!(feature = "heterogeneous") { + Some(quote!( + #[rtfm::export::shared] + )) + } else { + None + }; + + const_app.push(quote!( + #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..605171b8 --- /dev/null +++ b/macros/src/codegen/pre_init.rs @@ -0,0 +1,159 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::ast::App; + +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!( + // NOTE(transmute) to avoid debug_assertion in multi-core mode + let mut core: rtfm::export::Peripherals = core::mem::transmute(()); + )); + + 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.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(core, app.args.cores); + 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!(rtfm::export::NVIC::unmask(#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); + let shared = if cfg!(feature = "heterogeneous") { + Some(quote!( + #[rtfm::export::shared] + )) + } else { + None + }; + + const_app.push(quote!( + #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.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( + 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..bec46020 --- /dev/null +++ b/macros/src/codegen/resources.rs @@ -0,0 +1,125 @@ +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, section) = match loc { + Location::Owned { + core, + cross_initialized: false, + } => ( + util::cfg_core(*core, app.args.cores), + util::link_section("data", *core), + ), + + // shared `static`s and cross-initialized resources need to be in `.shared` memory + _ => ( + if cfg!(feature = "heterogeneous") { + Some(quote!(#[rtfm::export::shared])) + } else { + None + }, + 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; + const_app.push(quote!( + #[allow(non_upper_case_globals)] + #(#attrs)* + #(#cfgs)* + #loc_attr + #section + static mut #name: #ty = #expr; + )); + } + + if let Some(Ownership::Contended { ceiling }) = analysis.ownerships.get(name) { + let cfg_core = util::cfg_core(loc.core().expect("UNREACHABLE"), app.args.cores); + + mod_resources.push(quote!( + #[allow(non_camel_case_types)] + #(#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..07a60616 --- /dev/null +++ b/macros/src/codegen/resources_struct.rs @@ -0,0 +1,182 @@ +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, 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 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..8cf60985 --- /dev/null +++ b/macros/src/codegen/schedule.rs @@ -0,0 +1,99 @@ +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); + + let section = util::link_section("text", sender); + methods.push(quote!( + #(#cfgs)* + #section + 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); + + let section = util::link_section("text", sender); + items.push(quote!( + #cfg_sender + #(#cfgs)* + #section + 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..be1eb05c --- /dev/null +++ b/macros/src/codegen/software_tasks.rs @@ -0,0 +1,194 @@ +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, bss, mk_uninit): ( + _, + _, + _, + _, + Box<dyn Fn() -> Option<_>>, + ) = if receiver == sender { + ( + cfg_sender.clone(), + quote!(rtfm::export::SCFQ<#cap_ty>), + quote!(rtfm::export::Queue(unsafe { + rtfm::export::iQueue::u8_sc() + })), + util::link_section("bss", sender), + Box::new(|| util::link_section_uninit(Some(sender))), + ) + } else { + let shared = if cfg!(feature = "heterogeneous") { + Some(quote!(#[rtfm::export::shared])) + } else { + None + }; + + ( + shared, + quote!(rtfm::export::MCFQ<#cap_ty>), + quote!(rtfm::export::Queue(rtfm::export::iQueue::u8())), + None, + Box::new(|| util::link_section_uninit(None)), + ) + }; + let loc = &loc; + + const_app.push(quote!( + /// Queue version of a free-list that keeps track of empty slots in + /// the following buffers + #loc + #bss + 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); + + let uninit = mk_uninit(); + const_app.push(quote!( + #loc + #uninit + /// 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 uninit = mk_uninit(); + let inputs = util::inputs_ident(name, sender); + const_app.push(quote!( + #loc + #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, + ); + + 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, receiver, app); + + locals_pat = Some(pat); + root.push(struct_); + } + + let cfg_receiver = util::cfg_core(receiver, app.args.cores); + let section = util::link_section("text", receiver); + 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)] + #cfg_receiver + #section + 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..c63c410b --- /dev/null +++ b/macros/src/codegen/spawn.rs @@ -0,0 +1,131 @@ +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 + }; + + let section = util::link_section("text", sender); + methods.push(quote!( + #(#cfgs)* + #section + 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); + + let section = util::link_section("text", sender); + items.push(quote!( + #cfg_sender + #(#cfgs)* + #section + 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..98bce074 --- /dev/null +++ b/macros/src/codegen/spawn_body.rs @@ -0,0 +1,82 @@ +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 enum_ = util::interrupt_ident(receiver, app.args.cores); + let interrupt = &analysis.interrupts[&receiver][&priority]; + let pend = if sender != receiver { + quote!( + #device::xpend(#receiver, #device::#enum_::#interrupt); + ) + } else { + quote!( + rtfm::pend(#device::#enum_::#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..3af7e432 --- /dev/null +++ b/macros/src/codegen/timer_queue.rs @@ -0,0 +1,153 @@ +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>); + + let section = util::link_section("bss", sender); + items.push(quote!( + #cfg_sender + #[doc = #doc] + #section + 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 enum_ = util::interrupt_ident(receiver, app.args.cores); + let interrupt = &analysis.interrupts[&receiver][&priority]; + + let pend = if sender != receiver { + quote!( + #device::xpend(#receiver, #device::#enum_::#interrupt); + ) + } else { + quote!( + rtfm::pend(#device::#enum_::#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; + let sys_tick = util::suffixed("SysTick", sender); + let section = util::link_section("text", sender); + items.push(quote!( + #[no_mangle] + #cfg_sender + #section + unsafe fn #sys_tick() { + 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..207272dc --- /dev/null +++ b/macros/src/codegen/util.rs @@ -0,0 +1,325 @@ +use core::sync::atomic::{AtomicUsize, Ordering}; + +use proc_macro2::{Span, TokenStream as TokenStream2}; +use quote::quote; +use rtfm_syntax::{ast::App, Context, Core}; +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!(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 if cfg!(feature = "heterogeneous") { + let core = core.to_string(); + Some(quote!(#[cfg(core = #core)])) + } else { + None + } +} + +/// 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()) +} + +pub fn interrupt_ident(core: Core, cores: u8) -> Ident { + let span = Span::call_site(); + if cores == 1 { + Ident::new("Interrupt", span) + } else { + Ident::new(&format!("Interrupt_{}", core), 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) +} + +pub fn link_section(section: &str, core: Core) -> Option<TokenStream2> { + if cfg!(feature = "homogeneous") { + let section = format!(".{}_{}.rtfm{}", section, core, link_section_index()); + Some(quote!(#[link_section = #section])) + } else { + None + } +} + +// NOTE `None` means in shared memory +pub fn link_section_uninit(core: Option<Core>) -> Option<TokenStream2> { + let section = if let Some(core) = core { + let index = link_section_index(); + + if cfg!(feature = "homogeneous") { + format!(".uninit_{}.rtfm{}", core, index) + } else { + format!(".uninit.rtfm{}", index) + } + } else { + if cfg!(feature = "heterogeneous") { + // `#[shared]` attribute sets the linker section + return None; + } + + format!(".uninit.rtfm{}", 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(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: &[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(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(), + ) +} + +pub fn suffixed(name: &str, core: u8) -> Ident { + let span = Span::call_site(); + + if cfg!(feature = "homogeneous") { + Ident::new(&format!("{}_{}", name, core), span) + } else { + Ident::new(name, span) + } +} + +/// 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()) +} |