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