diff options
Diffstat (limited to 'macros/src/codegen.rs')
-rw-r--r-- | macros/src/codegen.rs | 2327 |
1 files changed, 121 insertions, 2206 deletions
diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs index 1b3f67b8..f230d395 100644 --- a/macros/src/codegen.rs +++ b/macros/src/codegen.rs @@ -1,2267 +1,182 @@ -#![deny(warnings)] - -use proc_macro::TokenStream; -use std::{ - collections::{BTreeMap, HashMap}, - time::{SystemTime, UNIX_EPOCH}, -}; - -use proc_macro2::Span; +use proc_macro2::TokenStream as TokenStream2; use quote::quote; -use rand::{Rng, SeedableRng}; -use syn::{parse_quote, ArgCaptured, Attribute, Ident, IntSuffix, LitInt}; - -use crate::{ - analyze::{Analysis, Ownership}, - syntax::{App, Idents, Static}, -}; - -// NOTE to avoid polluting the user namespaces we map some identifiers to pseudo-hygienic names. -// In some instances we also use the pseudo-hygienic names for safety, for example the user should -// not modify the priority field of resources. -type Aliases = BTreeMap<Ident, Ident>; - -struct Context { - // Alias - #[cfg(feature = "timer-queue")] - baseline: Ident, - dispatchers: BTreeMap<u8, Dispatcher>, - // Alias (`fn`) - idle: Ident, - // Alias (`fn`) - init: Ident, - // Alias - priority: Ident, - // For non-singletons this maps the resource name to its `static mut` variable name - statics: Aliases, - /// Task -> Alias (`struct`) - resources: HashMap<Kind, Resources>, - // Alias (`enum`) - schedule_enum: Ident, - // Task -> Alias (`fn`) - schedule_fn: Aliases, - tasks: BTreeMap<Ident, Task>, - // Alias (`struct` / `static mut`) - timer_queue: Ident, - // Generator of Ident names or suffixes - ident_gen: IdentGenerator, -} - -struct Dispatcher { - enum_: Ident, - ready_queue: Ident, -} - -struct Task { - alias: Ident, - free_queue: Ident, - inputs: Ident, - spawn_fn: Ident, - - #[cfg(feature = "timer-queue")] - scheduleds: Ident, -} - -impl Default for Context { - fn default() -> Self { - let mut ident_gen = IdentGenerator::new(); - - Context { - #[cfg(feature = "timer-queue")] - baseline: ident_gen.mk_ident(None, false), - dispatchers: BTreeMap::new(), - idle: ident_gen.mk_ident(Some("idle"), false), - init: ident_gen.mk_ident(Some("init"), false), - priority: ident_gen.mk_ident(None, false), - statics: Aliases::new(), - resources: HashMap::new(), - schedule_enum: ident_gen.mk_ident(None, false), - schedule_fn: Aliases::new(), - tasks: BTreeMap::new(), - timer_queue: ident_gen.mk_ident(None, false), - ident_gen, - } - } -} - -struct Resources { - alias: Ident, - decl: proc_macro2::TokenStream, -} - -pub fn app(app: &App, analysis: &Analysis) -> TokenStream { - let mut ctxt = Context::default(); - - let resources = resources(&mut ctxt, &app, analysis); - - let tasks = tasks(&mut ctxt, &app, analysis); - - let (dispatchers_data, dispatchers) = dispatchers(&mut ctxt, &app, analysis); - - let (init_fn, has_late_resources) = init(&mut ctxt, &app, analysis); - let init_arg = if cfg!(feature = "timer-queue") { - quote!(rtfm::Peripherals { - CBP: p.CBP, - CPUID: p.CPUID, - DCB: &mut p.DCB, - FPB: p.FPB, - FPU: p.FPU, - ITM: p.ITM, - MPU: p.MPU, - SCB: &mut p.SCB, - TPIU: p.TPIU, - }) - } else { - quote!(rtfm::Peripherals { - CBP: p.CBP, - CPUID: p.CPUID, - DCB: p.DCB, - DWT: p.DWT, - FPB: p.FPB, - FPU: p.FPU, - ITM: p.ITM, - MPU: p.MPU, - SCB: &mut p.SCB, - SYST: p.SYST, - TPIU: p.TPIU, - }) - }; - - let init = &ctxt.init; - let init_phase = if has_late_resources { - let assigns = app - .resources - .iter() - .filter_map(|(name, res)| { - if res.expr.is_none() { - let alias = &ctxt.statics[name]; - - Some(quote!(#alias.write(res.#name);)) - } else { - None - } - }) - .collect::<Vec<_>>(); - - quote!( - let res = #init(#init_arg); - #(#assigns)* - ) - } else { - quote!(#init(#init_arg);) - }; - - let post_init = post_init(&ctxt, &app, analysis); - - let (idle_fn, idle_expr) = idle(&mut ctxt, &app, analysis); - - let exceptions = exceptions(&mut ctxt, app, analysis); - - let (root_interrupts, scoped_interrupts) = interrupts(&mut ctxt, app, analysis); - - let spawn = spawn(&mut ctxt, app, analysis); - - let schedule = match () { - #[cfg(feature = "timer-queue")] - () => schedule(&ctxt, app), - #[cfg(not(feature = "timer-queue"))] - () => quote!(), - }; - - let timer_queue = timer_queue(&mut ctxt, app, analysis); - - let pre_init = pre_init(&ctxt, &app, analysis); - - let assertions = assertions(app, analysis); - - let main = ctxt.ident_gen.mk_ident(None, false); - quote!( - #resources - - #spawn - - #timer_queue - - #schedule - - #dispatchers_data - - #(#exceptions)* - - #root_interrupts - - const APP: () = { - #scoped_interrupts - - #(#dispatchers)* - }; - - #(#tasks)* - - #init_fn - - #idle_fn - - #[export_name = "main"] - #[allow(unsafe_code)] - #[doc(hidden)] - unsafe fn #main() -> ! { - #assertions - - rtfm::export::interrupt::disable(); - - #pre_init - - #init_phase - - #post_init - - rtfm::export::interrupt::enable(); - - #idle_expr - } - ) - .into() -} - -fn resources(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream { - let mut items = vec![]; - let mut module = vec![]; - for (name, res) in &app.resources { - let cfgs = &res.cfgs; - let attrs = &res.attrs; - let mut_ = &res.mutability; - let ty = &res.ty; - let expr = &res.expr; - - if res.singleton { - items.push(quote!( - #(#attrs)* - pub static #mut_ #name: #ty = #expr; - )); - - let alias = ctxt.ident_gen.mk_ident(None, true); // XXX is randomness required? - if let Some(Ownership::Shared { ceiling }) = analysis.ownerships.get(name) { - items.push(mk_resource( - ctxt, - cfgs, - name, - quote!(#name), - *ceiling, - quote!(&mut <#name as owned_singleton::Singleton>::new()), - app, - Some(&mut module), - )) - } - - ctxt.statics.insert(name.clone(), alias); - } else { - let alias = ctxt.ident_gen.mk_ident(None, false); - let symbol = format!("{}::{}", name, alias); - - items.push( - expr.as_ref() - .map(|expr| { - quote!( - #(#attrs)* - #(#cfgs)* - #[doc = #symbol] - static mut #alias: #ty = #expr; - ) - }) - .unwrap_or_else(|| { - quote!( - #(#attrs)* - #(#cfgs)* - #[doc = #symbol] - static mut #alias: rtfm::export::MaybeUninit<#ty> = - rtfm::export::MaybeUninit::uninit(); - ) - }), - ); - - if let Some(Ownership::Shared { ceiling }) = analysis.ownerships.get(name) { - if res.mutability.is_some() { - let ptr = if res.expr.is_none() { - quote!(unsafe { &mut *#alias.as_mut_ptr() }) - } else { - quote!(unsafe { &mut #alias }) - }; - - items.push(mk_resource( - ctxt, - cfgs, - name, - quote!(#ty), - *ceiling, - ptr, - app, - Some(&mut module), - )); - } - } - - ctxt.statics.insert(name.clone(), alias); - } - } - - if !module.is_empty() { - items.push(quote!( - /// Resource proxies - pub mod resources { - #(#module)* - } - )); - } - - quote!(#(#items)*) -} - -fn init(ctxt: &mut Context, app: &App, analysis: &Analysis) -> (proc_macro2::TokenStream, bool) { - let attrs = &app.init.attrs; - let locals = mk_locals(&app.init.statics, true); - let stmts = &app.init.stmts; - // TODO remove in v0.5.x - let assigns = app - .init - .assigns - .iter() - .map(|assign| { - let attrs = &assign.attrs; - if app - .resources - .get(&assign.left) - .map(|r| r.expr.is_none()) - .unwrap_or(false) - { - let alias = &ctxt.statics[&assign.left]; - let expr = &assign.right; - quote!( - #(#attrs)* - unsafe { #alias.write(#expr); } - ) - } else { - let left = &assign.left; - let right = &assign.right; - quote!( - #(#attrs)* - #left = #right; - ) - } - }) - .collect::<Vec<_>>(); - - let prelude = prelude( - ctxt, - Kind::Init, - &app.init.args.resources, - &app.init.args.spawn, - &app.init.args.schedule, - app, - 255, - analysis, - ); - - let (late_resources, late_resources_ident, ret) = if app.init.returns_late_resources { - // create `LateResources` struct in the root of the crate - let ident = ctxt.ident_gen.mk_ident(None, false); - - let fields = app - .resources - .iter() - .filter_map(|(name, res)| { - if res.expr.is_none() { - let ty = &res.ty; - Some(quote!(pub #name: #ty)) - } else { - None - } - }) - .collect::<Vec<_>>(); - - let late_resources = quote!( - #[allow(non_snake_case)] - pub struct #ident { - #(#fields),* - } - ); - - ( - Some(late_resources), - Some(ident), - Some(quote!(-> init::LateResources)), - ) - } else { - (None, None, None) - }; - let has_late_resources = late_resources.is_some(); - - let module = module( - ctxt, - Kind::Init, - !app.init.args.schedule.is_empty(), - !app.init.args.spawn.is_empty(), - app, - late_resources_ident, - ); - - #[cfg(feature = "timer-queue")] - let baseline = &ctxt.baseline; - let baseline_let = match () { - #[cfg(feature = "timer-queue")] - () => quote!(let ref #baseline = rtfm::Instant::artificial(0);), - - #[cfg(not(feature = "timer-queue"))] - () => quote!(), - }; - - let start_let = match () { - #[cfg(feature = "timer-queue")] - () => quote!( - #[allow(unused_variables)] - let start = *#baseline; - ), - #[cfg(not(feature = "timer-queue"))] - () => quote!(), - }; - - let unsafety = &app.init.unsafety; - let device = &app.args.device; - let init = &ctxt.init; - ( - quote!( - #late_resources - - #module - - // unsafe trampoline to deter end-users from calling this non-reentrant function - #(#attrs)* - unsafe fn #init(core: rtfm::Peripherals) #ret { - #[inline(always)] - #unsafety fn init(mut core: rtfm::Peripherals) #ret { - #(#locals)* - - #baseline_let - - #prelude - - let mut device = unsafe { #device::Peripherals::steal() }; - - #start_let - - #(#stmts)* - - #(#assigns)* - } - - init(core) - } - ), - has_late_resources, - ) -} - -fn post_init(ctxt: &Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream { - let mut exprs = vec![]; - - // TODO turn the assertions that check that the priority is not larger than what's supported by - // the device into compile errors - let device = &app.args.device; - let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS); - for (handler, exception) in &app.exceptions { - let name = exception.args.binds(handler); - let priority = exception.args.priority; - exprs.push(quote!(assert!(#priority <= (1 << #nvic_prio_bits)))); - exprs.push(quote!(p.SCB.set_priority( - rtfm::export::SystemHandler::#name, - ((1 << #nvic_prio_bits) - #priority) << (8 - #nvic_prio_bits), - ))); - } - - if !analysis.timer_queue.tasks.is_empty() { - let priority = analysis.timer_queue.priority; - exprs.push(quote!(assert!(#priority <= (1 << #nvic_prio_bits)))); - exprs.push(quote!(p.SCB.set_priority( - rtfm::export::SystemHandler::SysTick, - ((1 << #nvic_prio_bits) - #priority) << (8 - #nvic_prio_bits), - ))); - } - - if app.idle.is_none() { - // Set SLEEPONEXIT bit to enter sleep mode when returning from ISR - exprs.push(quote!(p.SCB.scr.modify(|r| r | 1 << 1))); - } - - // Enable and start the system timer - if !analysis.timer_queue.tasks.is_empty() { - let tq = &ctxt.timer_queue; - exprs.push( - quote!((*#tq.as_mut_ptr()).syst.set_clock_source(rtfm::export::SystClkSource::Core)), - ); - exprs.push(quote!((*#tq.as_mut_ptr()).syst.enable_counter())); - } - - // Enable cycle counter - if cfg!(feature = "timer-queue") { - exprs.push(quote!(p.DCB.enable_trace())); - exprs.push(quote!(p.DWT.enable_cycle_counter())); - } - - quote!(#(#exprs;)*) -} - -/// This function creates creates a module for `init` / `idle` / a `task` (see `kind` argument) -fn module( - ctxt: &mut Context, - kind: Kind, - schedule: bool, - spawn: bool, - app: &App, - late_resources: Option<Ident>, -) -> proc_macro2::TokenStream { - let mut items = vec![]; - let mut fields = vec![]; - - let name = kind.ident(); - let priority = &ctxt.priority; - let device = &app.args.device; - - let mut lt = None; - match kind { - Kind::Init => { - if cfg!(feature = "timer-queue") { - fields.push(quote!( - /// System start time = `Instant(0 /* cycles */)` - pub start: rtfm::Instant, - )); - } - - fields.push(quote!( - /// Core (Cortex-M) peripherals - pub core: rtfm::Peripherals<'a>, - /// Device specific peripherals - pub device: #device::Peripherals, - )); - lt = Some(quote!('a)); - } - Kind::Idle => {} - Kind::Exception(_) | Kind::Interrupt(_) => { - if cfg!(feature = "timer-queue") { - fields.push(quote!( - /// Time at which this handler started executing - pub start: rtfm::Instant, - )); - } - } - Kind::Task(_) => { - if cfg!(feature = "timer-queue") { - fields.push(quote!( - /// The time at which this task was scheduled to run - pub scheduled: rtfm::Instant, - )); - } - } - } - - if schedule { - lt = Some(quote!('a)); - - fields.push(quote!( - /// Tasks that can be scheduled from this context - pub schedule: Schedule<'a>, - )); - - items.push(quote!( - /// Tasks that can be scheduled from this context - #[derive(Clone, Copy)] - pub struct Schedule<'a> { - #[doc(hidden)] - pub #priority: &'a rtfm::export::Priority, - } - )); - } - - if spawn { - lt = Some(quote!('a)); - - fields.push(quote!( - /// Tasks that can be spawned from this context - pub spawn: Spawn<'a>, - )); - - if kind.is_idle() { - items.push(quote!( - /// Tasks that can be spawned from this context - #[derive(Clone, Copy)] - pub struct Spawn<'a> { - #[doc(hidden)] - pub #priority: &'a rtfm::export::Priority, - } - )); - } else { - let baseline_field = match () { - #[cfg(feature = "timer-queue")] - () => { - let baseline = &ctxt.baseline; - quote!( - // NOTE this field is visible so we use a shared reference to make it - // immutable - #[doc(hidden)] - pub #baseline: &'a rtfm::Instant, - ) - } - #[cfg(not(feature = "timer-queue"))] - () => quote!(), - }; - - items.push(quote!( - /// Tasks that can be spawned from this context - #[derive(Clone, Copy)] - pub struct Spawn<'a> { - #baseline_field - #[doc(hidden)] - pub #priority: &'a rtfm::export::Priority, - } - )); - } - } - - let mut root = None; - if let Some(resources) = ctxt.resources.get(&kind) { - lt = Some(quote!('a)); - - root = Some(resources.decl.clone()); - - let alias = &resources.alias; - items.push(quote!( - #[doc(inline)] - pub use super::#alias as Resources; - )); - - fields.push(quote!( - /// Resources available in this context - pub resources: Resources<'a>, - )); - }; - - let doc = match kind { - Kind::Exception(_) => "Exception handler", - Kind::Idle => "Idle loop", - Kind::Init => "Initialization function", - Kind::Interrupt(_) => "Interrupt handler", - Kind::Task(_) => "Software task", - }; - - if let Some(late_resources) = late_resources { - items.push(quote!( - pub use super::#late_resources as LateResources; - )); - } - - quote!( - #root - - #[doc = #doc] - #[allow(non_snake_case)] - pub mod #name { - /// Variables injected into this context by the `app` attribute - pub struct Context<#lt> { - #(#fields)* - } - - #(#items)* - } - ) -} - -/// The prelude injects `resources`, `spawn`, `schedule` and `start` / `scheduled` (all values) into -/// a function scope -fn prelude( - ctxt: &mut Context, - kind: Kind, - resources: &Idents, - spawn: &Idents, - schedule: &Idents, - app: &App, - logical_prio: u8, - analysis: &Analysis, -) -> proc_macro2::TokenStream { - let mut items = vec![]; - - let lt = if kind.runs_once() { - quote!('static) - } else { - quote!('a) - }; - - let module = kind.ident(); - - let priority = &ctxt.priority; - if !resources.is_empty() { - let mut defs = vec![]; - let mut exprs = vec![]; - - // NOTE This field is just to avoid unused type parameter errors around `'a` - defs.push(quote!(#[allow(dead_code)] pub #priority: &'a rtfm::export::Priority)); - exprs.push(parse_quote!(#priority)); - - let mut may_call_lock = false; - let mut needs_unsafe = false; - for name in resources { - let res = &app.resources[name]; - let cfgs = &res.cfgs; - - let initialized = res.expr.is_some(); - let singleton = res.singleton; - let mut_ = res.mutability; - let ty = &res.ty; - - if kind.is_init() { - let mut force_mut = false; - if !analysis.ownerships.contains_key(name) { - // owned by Init - if singleton { - needs_unsafe = true; - defs.push(quote!( - #(#cfgs)* - pub #name: #name - )); - exprs.push(quote!( - #(#cfgs)* - #name: <#name as owned_singleton::Singleton>::new() - )); - continue; - } else { - defs.push(quote!( - #(#cfgs)* - pub #name: &'static #mut_ #ty - )); - } - } else { - // owned by someone else - if singleton { - needs_unsafe = true; - defs.push(quote!( - #(#cfgs)* - pub #name: &'a mut #name - )); - exprs.push(quote!( - #(#cfgs)* - #name: &mut <#name as owned_singleton::Singleton>::new() - )); - continue; - } else { - force_mut = true; - defs.push(quote!( - #(#cfgs)* - pub #name: &'a mut #ty - )); - } - } - - let alias = &ctxt.statics[name]; - // Resources assigned to init are always const initialized - needs_unsafe = true; - if force_mut { - exprs.push(quote!( - #(#cfgs)* - #name: &mut #alias - )); - } else { - exprs.push(quote!( - #(#cfgs)* - #name: &#mut_ #alias - )); - } - } else { - let ownership = &analysis.ownerships[name]; - let mut exclusive = false; - - if ownership.needs_lock(logical_prio) { - may_call_lock = true; - if singleton { - if mut_.is_none() { - needs_unsafe = true; - defs.push(quote!( - #(#cfgs)* - pub #name: &'a #name - )); - exprs.push(quote!( - #(#cfgs)* - #name: &<#name as owned_singleton::Singleton>::new() - )); - continue; - } else { - // Generate a resource proxy - defs.push(quote!( - #(#cfgs)* - pub #name: resources::#name<'a> - )); - exprs.push(quote!( - #(#cfgs)* - #name: resources::#name { #priority } - )); - continue; - } - } else { - if mut_.is_none() { - defs.push(quote!( - #(#cfgs)* - pub #name: &'a #ty - )); - } else { - // Generate a resource proxy - defs.push(quote!( - #(#cfgs)* - pub #name: resources::#name<'a> - )); - exprs.push(quote!( - #(#cfgs)* - #name: resources::#name { #priority } - )); - continue; - } - } - } else { - if singleton { - if kind.runs_once() { - needs_unsafe = true; - defs.push(quote!( - #(#cfgs)* - pub #name: #name - )); - exprs.push(quote!( - #(#cfgs)* - #name: <#name as owned_singleton::Singleton>::new() - )); - } else { - needs_unsafe = true; - if ownership.is_owned() || mut_.is_none() { - defs.push(quote!( - #(#cfgs)* - pub #name: &'a #mut_ #name - )); - // XXX is randomness required? - let alias = ctxt.ident_gen.mk_ident(None, true); - items.push(quote!( - #(#cfgs)* - let #mut_ #alias = unsafe { - <#name as owned_singleton::Singleton>::new() - }; - )); - exprs.push(quote!( - #(#cfgs)* - #name: &#mut_ #alias - )); - } else { - may_call_lock = true; - defs.push(quote!( - #(#cfgs)* - pub #name: rtfm::Exclusive<'a, #name> - )); - // XXX is randomness required? - let alias = ctxt.ident_gen.mk_ident(None, true); - items.push(quote!( - #(#cfgs)* - let #mut_ #alias = unsafe { - <#name as owned_singleton::Singleton>::new() - }; - )); - exprs.push(quote!( - #(#cfgs)* - #name: rtfm::Exclusive(&mut #alias) - )); - } - } - continue; - } else { - if ownership.is_owned() || mut_.is_none() { - defs.push(quote!( - #(#cfgs)* - pub #name: &#lt #mut_ #ty - )); - } else { - exclusive = true; - may_call_lock = true; - defs.push(quote!( - #(#cfgs)* - pub #name: rtfm::Exclusive<#lt, #ty> - )); - } - } - } - - let alias = &ctxt.statics[name]; - needs_unsafe = true; - if initialized { - if exclusive { - exprs.push(quote!( - #(#cfgs)* - #name: rtfm::Exclusive(&mut #alias) - )); - } else { - exprs.push(quote!( - #(#cfgs)* - #name: &#mut_ #alias - )); - } - } else { - let expr = if mut_.is_some() { - quote!(&mut *#alias.as_mut_ptr()) - } else { - quote!(&*#alias.as_ptr()) - }; - - if exclusive { - exprs.push(quote!( - #(#cfgs)* - #name: rtfm::Exclusive(#expr) - )); - } else { - exprs.push(quote!( - #(#cfgs)* - #name: #expr - )); - } - } - } - } - - let alias = ctxt.ident_gen.mk_ident(None, false); - let unsafety = if needs_unsafe { - Some(quote!(unsafe)) - } else { - None - }; - - let defs = &defs; - let doc = format!("`{}::Resources`", kind.ident().to_string()); - let decl = quote!( - #[doc = #doc] - #[allow(non_snake_case)] - pub struct #alias<'a> { #(#defs,)* } - ); - items.push(quote!( - #[allow(unused_variables)] - #[allow(unsafe_code)] - #[allow(unused_mut)] - let mut resources = #unsafety { #alias { #(#exprs,)* } }; - )); - - ctxt.resources - .insert(kind.clone(), Resources { alias, decl }); - - if may_call_lock { - items.push(quote!( - use rtfm::Mutex; - )); - } - } - - if !spawn.is_empty() { - if kind.is_idle() { - items.push(quote!( - #[allow(unused_variables)] - let spawn = #module::Spawn { #priority }; - )); - } else { - let baseline_expr = match () { - #[cfg(feature = "timer-queue")] - () => { - let baseline = &ctxt.baseline; - quote!(#baseline) - } - #[cfg(not(feature = "timer-queue"))] - () => quote!(), - }; - items.push(quote!( - #[allow(unused_variables)] - let spawn = #module::Spawn { #priority, #baseline_expr }; - )); - } - } - - if !schedule.is_empty() { - // Populate `schedule_fn` - for task in schedule { - if ctxt.schedule_fn.contains_key(task) { - continue; - } - - ctxt.schedule_fn - .insert(task.clone(), ctxt.ident_gen.mk_ident(None, false)); - } - - items.push(quote!( - #[allow(unused_imports)] - use rtfm::U32Ext; - - #[allow(unused_variables)] - let schedule = #module::Schedule { #priority }; - )); - } - - if items.is_empty() { - quote!() - } else { - quote!( - let ref #priority = unsafe { rtfm::export::Priority::new(#logical_prio) }; - - #(#items)* - ) - } -} - -fn idle( - ctxt: &mut Context, - app: &App, - analysis: &Analysis, -) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { - if let Some(idle) = app.idle.as_ref() { - let attrs = &idle.attrs; - let locals = mk_locals(&idle.statics, true); - let stmts = &idle.stmts; - - let prelude = prelude( - ctxt, - Kind::Idle, - &idle.args.resources, - &idle.args.spawn, - &idle.args.schedule, - app, - 0, - analysis, - ); - - let module = module( - ctxt, - Kind::Idle, - !idle.args.schedule.is_empty(), - !idle.args.spawn.is_empty(), - app, - None, - ); - - let unsafety = &idle.unsafety; - let idle = &ctxt.idle; - - ( - quote!( - #module - - // unsafe trampoline to deter end-users from calling this non-reentrant function - #(#attrs)* - unsafe fn #idle() -> ! { - #[inline(always)] - #unsafety fn idle() -> ! { - #(#locals)* - - #prelude - - #(#stmts)* - } - - idle() - } - ), - quote!(#idle()), - ) - } else { - ( - quote!(), - quote!(loop { - rtfm::export::wfi(); - }), - ) - } -} - -fn exceptions(ctxt: &mut Context, app: &App, analysis: &Analysis) -> Vec<proc_macro2::TokenStream> { - app.exceptions - .iter() - .map(|(ident, exception)| { - let attrs = &exception.attrs; - let stmts = &exception.stmts; - - let kind = Kind::Exception(ident.clone()); - let prelude = prelude( - ctxt, - kind.clone(), - &exception.args.resources, - &exception.args.spawn, - &exception.args.schedule, - app, - exception.args.priority, - analysis, - ); - - let module = module( - ctxt, - kind, - !exception.args.schedule.is_empty(), - !exception.args.spawn.is_empty(), - app, - None, - ); - - #[cfg(feature = "timer-queue")] - let baseline = &ctxt.baseline; - let baseline_let = match () { - #[cfg(feature = "timer-queue")] - () => quote!(let ref #baseline = rtfm::Instant::now();), - #[cfg(not(feature = "timer-queue"))] - () => quote!(), - }; - - let start_let = match () { - #[cfg(feature = "timer-queue")] - () => quote!( - #[allow(unused_variables)] - let start = *#baseline; - ), - #[cfg(not(feature = "timer-queue"))] - () => quote!(), - }; - - let locals = mk_locals(&exception.statics, false); - let symbol = exception.args.binds(ident).to_string(); - let alias = ctxt.ident_gen.mk_ident(None, false); - let unsafety = &exception.unsafety; - quote!( - #module - - // unsafe trampoline to deter end-users from calling this non-reentrant function - #[export_name = #symbol] - #(#attrs)* - unsafe fn #alias() { - #[inline(always)] - #unsafety fn exception() { - #(#locals)* - - #baseline_let - - #prelude - - #start_let - - rtfm::export::run(move || { - #(#stmts)* - }) - } - - exception() - } - ) - }) - .collect() -} - -fn interrupts( - ctxt: &mut Context, - app: &App, - analysis: &Analysis, -) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { +use rtic_syntax::ast::App; + +use crate::{analyze::Analysis, check::Extra}; + +mod assertions; +mod dispatchers; +mod hardware_tasks; +mod idle; +mod init; +mod locals; +mod module; +mod post_init; +mod pre_init; +mod resources; +mod resources_struct; +mod schedule; +mod schedule_body; +mod software_tasks; +mod spawn; +mod spawn_body; +mod timer_queue; +mod util; + +// TODO document the syntax here or in `rtic-syntax` +pub fn app(app: &App, analysis: &Analysis, extra: &Extra) -> TokenStream2 { + let mut mod_app = vec![]; + let mut mod_app_imports = vec![]; + let mut mains = vec![]; let mut root = vec![]; - let mut scoped = vec![]; - - for (ident, interrupt) in &app.interrupts { - let attrs = &interrupt.attrs; - let stmts = &interrupt.stmts; - - let kind = Kind::Interrupt(ident.clone()); - let prelude = prelude( - ctxt, - kind.clone(), - &interrupt.args.resources, - &interrupt.args.spawn, - &interrupt.args.schedule, - app, - interrupt.args.priority, - analysis, - ); - - root.push(module( - ctxt, - kind, - !interrupt.args.schedule.is_empty(), - !interrupt.args.spawn.is_empty(), - app, - None, - )); - - #[cfg(feature = "timer-queue")] - let baseline = &ctxt.baseline; - let baseline_let = match () { - #[cfg(feature = "timer-queue")] - () => quote!(let ref #baseline = rtfm::Instant::now();), - #[cfg(not(feature = "timer-queue"))] - () => quote!(), - }; - - let start_let = match () { - #[cfg(feature = "timer-queue")] - () => quote!( - #[allow(unused_variables)] - let start = *#baseline; - ), - #[cfg(not(feature = "timer-queue"))] - () => quote!(), - }; - - let locals = mk_locals(&interrupt.statics, false); - let alias = ctxt.ident_gen.mk_ident(None, false); - let symbol = interrupt.args.binds(ident).to_string(); - let unsafety = &interrupt.unsafety; - scoped.push(quote!( - // unsafe trampoline to deter end-users from calling this non-reentrant function - #(#attrs)* - #[export_name = #symbol] - unsafe fn #alias() { - #[inline(always)] - #unsafety fn interrupt() { - #(#locals)* - - #baseline_let - - #prelude - - #start_let - - rtfm::export::run(move || { - #(#stmts)* - }) - } - - interrupt() - } - )); - } - - (quote!(#(#root)*), quote!(#(#scoped)*)) -} - -fn tasks(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream { - let mut items = vec![]; - - // first pass to generate buffers (statics and resources) and spawn aliases - for (name, task) in &app.tasks { - #[cfg(feature = "timer-queue")] - let scheduleds_alias = ctxt.ident_gen.mk_ident(None, false); - let free_alias = ctxt.ident_gen.mk_ident(None, false); - let inputs_alias = ctxt.ident_gen.mk_ident(None, false); - let task_alias = ctxt.ident_gen.mk_ident(Some(&name.to_string()), false); - - let inputs = &task.inputs; - - let ty = tuple_ty(inputs); - - let capacity = analysis.capacities[name]; - let capacity_lit = mk_capacity_literal(capacity); - let capacity_ty = mk_typenum_capacity(capacity, true); - - let resource = mk_resource( - ctxt, - &[], - &free_alias, - quote!(rtfm::export::FreeQueue<#capacity_ty>), - *analysis.free_queues.get(name).unwrap_or(&0), - if cfg!(feature = "nightly") { - quote!(&mut #free_alias) - } else { - quote!(#free_alias.get_mut()) - }, - app, - None, - ); - - let scheduleds_static = match () { - #[cfg(feature = "timer-queue")] - () => { - let scheduleds_symbol = format!("{}::SCHEDULED_TIMES::{}", name, scheduleds_alias); - - if cfg!(feature = "nightly") { - let inits = - (0..capacity).map(|_| quote!(rtfm::export::MaybeUninit::uninit())); - - quote!( - #[doc = #scheduleds_symbol] - static mut #scheduleds_alias: - [rtfm::export::MaybeUninit<rtfm::Instant>; #capacity_lit] = - [#(#inits),*]; - ) - } else { - quote!( - #[doc = #scheduleds_symbol] - static mut #scheduleds_alias: - rtfm::export::MaybeUninit<[rtfm::Instant; #capacity_lit]> = - rtfm::export::MaybeUninit::uninit(); - ) - } - } - #[cfg(not(feature = "timer-queue"))] - () => quote!(), - }; - - let inputs_symbol = format!("{}::INPUTS::{}", name, inputs_alias); - let free_symbol = format!("{}::FREE_QUEUE::{}", name, free_alias); - if cfg!(feature = "nightly") { - let inits = (0..capacity).map(|_| quote!(rtfm::export::MaybeUninit::uninit())); - - items.push(quote!( - #[doc = #free_symbol] - static mut #free_alias: rtfm::export::FreeQueue<#capacity_ty> = unsafe { - rtfm::export::FreeQueue::new_sc() - }; - - #[doc = #inputs_symbol] - static mut #inputs_alias: [rtfm::export::MaybeUninit<#ty>; #capacity_lit] = - [#(#inits),*]; - )); - } else { - items.push(quote!( - #[doc = #free_symbol] - static mut #free_alias: rtfm::export::MaybeUninit< - rtfm::export::FreeQueue<#capacity_ty> - > = rtfm::export::MaybeUninit::uninit(); - - #[doc = #inputs_symbol] - static mut #inputs_alias: rtfm::export::MaybeUninit<[#ty; #capacity_lit]> = - rtfm::export::MaybeUninit::uninit(); - - )); - } - - items.push(quote!( - #resource - - #scheduleds_static - )); - - ctxt.tasks.insert( - name.clone(), - Task { - alias: task_alias, - free_queue: free_alias, - inputs: inputs_alias, - spawn_fn: ctxt.ident_gen.mk_ident(None, false), - - #[cfg(feature = "timer-queue")] - scheduleds: scheduleds_alias, - }, - ); - } + let mut user = vec![]; + let mut imports = vec![]; - // second pass to generate the actual task function - for (name, task) in &app.tasks { - let inputs = &task.inputs; - let locals = mk_locals(&task.statics, false); - let stmts = &task.stmts; - let unsafety = &task.unsafety; + // Generate the `main` function + let assertion_stmts = assertions::codegen(analysis); - let scheduled_let = match () { - #[cfg(feature = "timer-queue")] - () => { - let baseline = &ctxt.baseline; - quote!(let scheduled = *#baseline;) - } - #[cfg(not(feature = "timer-queue"))] - () => quote!(), - }; + let pre_init_stmts = pre_init::codegen(&app, analysis, extra); - let prelude = prelude( - ctxt, - Kind::Task(name.clone()), - &task.args.resources, - &task.args.spawn, - &task.args.schedule, - app, - task.args.priority, - analysis, - ); + let (mod_app_init, root_init, user_init, user_init_imports, call_init) = + init::codegen(app, analysis, extra); - items.push(module( - ctxt, - Kind::Task(name.clone()), - !task.args.schedule.is_empty(), - !task.args.spawn.is_empty(), - app, - None, - )); + let post_init_stmts = post_init::codegen(&app, analysis); - let attrs = &task.attrs; - let cfgs = &task.cfgs; - let task_alias = &ctxt.tasks[name].alias; - let (baseline, baseline_arg) = match () { - #[cfg(feature = "timer-queue")] - () => { - let baseline = &ctxt.baseline; - (quote!(#baseline,), quote!(#baseline: &rtfm::Instant,)) - } - #[cfg(not(feature = "timer-queue"))] - () => (quote!(), quote!()), - }; - let pats = tuple_pat(inputs); - items.push(quote!( - // unsafe trampoline to deter end-users from calling this non-reentrant function - #(#attrs)* - #(#cfgs)* - unsafe fn #task_alias(#baseline_arg #(#inputs,)*) { - #[inline(always)] - #unsafety fn task(#baseline_arg #(#inputs,)*) { - #(#locals)* + let (mod_app_idle, root_idle, user_idle, user_idle_imports, call_idle) = + idle::codegen(app, analysis, extra); - #prelude - - #scheduled_let - - #(#stmts)* - } - - task(#baseline #pats) - } - )); - } - - quote!(#(#items)*) -} - -fn dispatchers( - ctxt: &mut Context, - app: &App, - analysis: &Analysis, -) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { - let mut data = vec![]; - let mut dispatchers = vec![]; - - let device = &app.args.device; - for (level, dispatcher) in &analysis.dispatchers { - let ready_alias = ctxt.ident_gen.mk_ident(None, false); - let enum_alias = ctxt.ident_gen.mk_ident(None, false); - let capacity = mk_typenum_capacity(dispatcher.capacity, true); - - let variants = dispatcher - .tasks - .iter() - .map(|task| { - let task_ = &app.tasks[task]; - let cfgs = &task_.cfgs; - - quote!( - #(#cfgs)* - #task - ) - }) - .collect::<Vec<_>>(); - let symbol = format!("P{}::READY_QUEUE::{}", level, ready_alias); - let e = quote!(rtfm::export); - let ty = quote!(#e::ReadyQueue<#enum_alias, #capacity>); - let ceiling = *analysis.ready_queues.get(&level).unwrap_or(&0); - let resource = mk_resource( - ctxt, - &[], - &ready_alias, - ty.clone(), - ceiling, - if cfg!(feature = "nightly") { - quote!(&mut #ready_alias) - } else { - quote!(#ready_alias.get_mut()) - }, - app, - None, - ); - - if cfg!(feature = "nightly") { - data.push(quote!( - #[doc = #symbol] - static mut #ready_alias: #ty = unsafe { #e::ReadyQueue::new_sc() }; - )); - } else { - data.push(quote!( - #[doc = #symbol] - static mut #ready_alias: #e::MaybeUninit<#ty> = #e::MaybeUninit::uninit(); - )); - } - data.push(quote!( - #[allow(dead_code)] - #[allow(non_camel_case_types)] - enum #enum_alias { #(#variants,)* } - - #resource - )); - - let arms = dispatcher - .tasks - .iter() - .map(|task| { - let task_ = &ctxt.tasks[task]; - let inputs = &task_.inputs; - let free = &task_.free_queue; - let alias = &task_.alias; - - let task__ = &app.tasks[task]; - let pats = tuple_pat(&task__.inputs); - let cfgs = &task__.cfgs; - - let baseline_let; - let call; - match () { - #[cfg(feature = "timer-queue")] - () => { - let scheduleds = &task_.scheduleds; - let scheduled = if cfg!(feature = "nightly") { - quote!(#scheduleds.get_unchecked(usize::from(index)).as_ptr()) - } else { - quote!(#scheduleds.get_ref().get_unchecked(usize::from(index))) - }; - - baseline_let = quote!( - let baseline = ptr::read(#scheduled); - ); - call = quote!(#alias(&baseline, #pats)); - } - #[cfg(not(feature = "timer-queue"))] - () => { - baseline_let = quote!(); - call = quote!(#alias(#pats)); - } - }; - - let (free_, input) = if cfg!(feature = "nightly") { - ( - quote!(#free), - quote!(#inputs.get_unchecked(usize::from(index)).as_ptr()), - ) - } else { - ( - quote!(#free.get_mut()), - quote!(#inputs.get_ref().get_unchecked(usize::from(index))), - ) - }; - - quote!( - #(#cfgs)* - #enum_alias::#task => { - #baseline_let - let input = ptr::read(#input); - #free_.split().0.enqueue_unchecked(index); - let (#pats) = input; - #call - } - ) - }) - .collect::<Vec<_>>(); - - let attrs = &dispatcher.attrs; - let interrupt = &dispatcher.interrupt; - let symbol = interrupt.to_string(); - let alias = ctxt.ident_gen.mk_ident(None, false); - let ready_alias_ = if cfg!(feature = "nightly") { - quote!(#ready_alias) - } else { - quote!(#ready_alias.get_mut()) - }; - dispatchers.push(quote!( - #(#attrs)* - #[export_name = #symbol] - unsafe fn #alias() { - use core::ptr; - - // check that this interrupt exists - let _ = #device::interrupt::#interrupt; - - rtfm::export::run(|| { - while let Some((task, index)) = #ready_alias_.split().1.dequeue() { - match task { - #(#arms)* - } - } - }); - } - )); - - ctxt.dispatchers.insert( - *level, - Dispatcher { - ready_queue: ready_alias, - enum_: enum_alias, - }, - ); - } - - (quote!(#(#data)*), quote!(#(#dispatchers)*)) -} - -fn spawn(ctxt: &Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream { - let mut items = vec![]; - - // Generate `spawn` functions - let device = &app.args.device; - let priority = &ctxt.priority; - #[cfg(feature = "timer-queue")] - let baseline = &ctxt.baseline; - for (name, task) in &ctxt.tasks { - let alias = &task.spawn_fn; - let task_ = &app.tasks[name]; - let cfgs = &task_.cfgs; - let free = &task.free_queue; - let level = task_.args.priority; - let dispatcher = &ctxt.dispatchers[&level]; - let ready = &dispatcher.ready_queue; - let enum_ = &dispatcher.enum_; - let dispatcher = &analysis.dispatchers[&level].interrupt; - let inputs = &task.inputs; - let args = &task_.inputs; - let ty = tuple_ty(args); - let pats = tuple_pat(args); - - let scheduleds_write = match () { - #[cfg(feature = "timer-queue")] - () => { - let scheduleds = &ctxt.tasks[name].scheduleds; - if cfg!(feature = "nightly") { - quote!( - ptr::write( - #scheduleds.get_unchecked_mut(usize::from(index)).as_mut_ptr(), - #baseline, - ); - ) - } else { - quote!( - ptr::write( - #scheduleds.get_mut().get_unchecked_mut(usize::from(index)), - #baseline, - ); - ) - } - } - #[cfg(not(feature = "timer-queue"))] - () => quote!(), - }; - - let baseline_arg = match () { - #[cfg(feature = "timer-queue")] - () => quote!(#baseline: rtfm::Instant,), - #[cfg(not(feature = "timer-queue"))] - () => quote!(), - }; - - let input = if cfg!(feature = "nightly") { - quote!(#inputs.get_unchecked_mut(usize::from(index)).as_mut_ptr()) - } else { - quote!(#inputs.get_mut().get_unchecked_mut(usize::from(index))) - }; - items.push(quote!( - #[inline(always)] - #(#cfgs)* - unsafe fn #alias( - #baseline_arg - #priority: &rtfm::export::Priority, - #(#args,)* - ) -> Result<(), #ty> { - use core::ptr; - - use rtfm::Mutex; - - if let Some(index) = (#free { #priority }).lock(|f| f.split().1.dequeue()) { - ptr::write(#input, (#pats)); - #scheduleds_write - - #ready { #priority }.lock(|rq| { - rq.split().0.enqueue_unchecked((#enum_::#name, index)) - }); - - rtfm::pend(#device::Interrupt::#dispatcher); - - Ok(()) - } else { - Err((#pats)) - } - } + if user_init.is_some() { + mod_app_imports.push(quote!( + use super::init; )) } - - // Generate `spawn` structs; these call the `spawn` functions generated above - for (name, spawn) in app.spawn_callers() { - if spawn.is_empty() { - continue; - } - - #[cfg(feature = "timer-queue")] - let is_idle = name.to_string() == "idle"; - - let mut methods = vec![]; - for task in spawn { - let task_ = &app.tasks[task]; - let alias = &ctxt.tasks[task].spawn_fn; - let inputs = &task_.inputs; - let cfgs = &task_.cfgs; - let ty = tuple_ty(inputs); - let pats = tuple_pat(inputs); - - let instant = match () { - #[cfg(feature = "timer-queue")] - () => { - if is_idle { - quote!(rtfm::Instant::now(),) - } else { - quote!(*self.#baseline,) - } - } - #[cfg(not(feature = "timer-queue"))] - () => quote!(), - }; - methods.push(quote!( - #[allow(unsafe_code)] - #[inline] - #(#cfgs)* - pub fn #task(&self, #(#inputs,)*) -> Result<(), #ty> { - unsafe { #alias(#instant &self.#priority, #pats) } - } - )); - } - - items.push(quote!( - impl<'a> #name::Spawn<'a> { - #(#methods)* - } - )); - } - - quote!(#(#items)*) -} - -#[cfg(feature = "timer-queue")] -fn schedule(ctxt: &Context, app: &App) -> proc_macro2::TokenStream { - let mut items = vec![]; - - // Generate `schedule` functions - let priority = &ctxt.priority; - let timer_queue = &ctxt.timer_queue; - for (task, alias) in &ctxt.schedule_fn { - let task_ = &ctxt.tasks[task]; - let free = &task_.free_queue; - let enum_ = &ctxt.schedule_enum; - let inputs = &task_.inputs; - let scheduleds = &task_.scheduleds; - let task__ = &app.tasks[task]; - let args = &task__.inputs; - let cfgs = &task__.cfgs; - let ty = tuple_ty(args); - let pats = tuple_pat(args); - - let input = if cfg!(feature = "nightly") { - quote!(#inputs.get_unchecked_mut(usize::from(index)).as_mut_ptr()) - } else { - quote!(#inputs.get_mut().get_unchecked_mut(usize::from(index))) - }; - - let scheduled = if cfg!(feature = "nightly") { - quote!(#scheduleds.get_unchecked_mut(usize::from(index)).as_mut_ptr()) - } else { - quote!(#scheduleds.get_mut().get_unchecked_mut(usize::from(index))) - }; - items.push(quote!( - #[inline(always)] - #(#cfgs)* - unsafe fn #alias( - #priority: &rtfm::export::Priority, - instant: rtfm::Instant, - #(#args,)* - ) -> Result<(), #ty> { - use core::ptr; - - use rtfm::Mutex; - - if let Some(index) = (#free { #priority }).lock(|f| f.split().1.dequeue()) { - ptr::write(#input, (#pats)); - ptr::write(#scheduled, instant); - - let nr = rtfm::export::NotReady { - instant, - index, - task: #enum_::#task, - }; - - ({#timer_queue { #priority }}).lock(|tq| tq.enqueue_unchecked(nr)); - - Ok(()) - } else { - Err((#pats)) - } - } + if user_idle.is_some() { + mod_app_imports.push(quote!( + use super::idle; )) } - // Generate `Schedule` structs; these call the `schedule` functions generated above - for (name, schedule) in app.schedule_callers() { - if schedule.is_empty() { - continue; - } - - debug_assert!(!schedule.is_empty()); - - let mut methods = vec![]; - for task in schedule { - let alias = &ctxt.schedule_fn[task]; - let task_ = &app.tasks[task]; - let inputs = &task_.inputs; - let cfgs = &task_.cfgs; - let ty = tuple_ty(inputs); - let pats = tuple_pat(inputs); + user.push(quote!( + #user_init - methods.push(quote!( - #[inline] - #(#cfgs)* - pub fn #task( - &self, - instant: rtfm::Instant, - #(#inputs,)* - ) -> Result<(), #ty> { - unsafe { #alias(&self.#priority, instant, #pats) } - } - )); - } - - items.push(quote!( - impl<'a> #name::Schedule<'a> { - #(#methods)* - } - )); - } - - quote!(#(#items)*) -} - -fn timer_queue(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream { - let tasks = &analysis.timer_queue.tasks; - - if tasks.is_empty() { - return quote!(); - } - - let mut items = vec![]; - - let variants = tasks - .iter() - .map(|task| { - let cfgs = &app.tasks[task].cfgs; - quote!( - #(#cfgs)* - #task - ) - }) - .collect::<Vec<_>>(); - let enum_ = &ctxt.schedule_enum; - items.push(quote!( - #[allow(dead_code)] - #[allow(non_camel_case_types)] - #[derive(Clone, Copy)] - enum #enum_ { #(#variants,)* } + #user_idle )); - let cap = mk_typenum_capacity(analysis.timer_queue.capacity, false); - let tq = &ctxt.timer_queue; - let symbol = format!("TIMER_QUEUE::{}", tq); - if cfg!(feature = "nightly") { - items.push(quote!( - #[doc = #symbol] - static mut #tq: rtfm::export::MaybeUninit<rtfm::export::TimerQueue<#enum_, #cap>> = - rtfm::export::MaybeUninit::uninit(); - )); - } else { - items.push(quote!( - #[doc = #symbol] - static mut #tq: - rtfm::export::MaybeUninit<rtfm::export::TimerQueue<#enum_, #cap>> = - rtfm::export::MaybeUninit::uninit(); - )); - } - - items.push(mk_resource( - ctxt, - &[], - tq, - quote!(rtfm::export::TimerQueue<#enum_, #cap>), - analysis.timer_queue.ceiling, - quote!(&mut *#tq.as_mut_ptr()), - app, - None, + imports.push(quote!( + #(#user_init_imports)* + #(#user_idle_imports)* )); - let priority = &ctxt.priority; - let device = &app.args.device; - let arms = tasks - .iter() - .map(|task| { - let task_ = &app.tasks[task]; - let level = task_.args.priority; - let cfgs = &task_.cfgs; - let dispatcher_ = &ctxt.dispatchers[&level]; - let tenum = &dispatcher_.enum_; - let ready = &dispatcher_.ready_queue; - let dispatcher = &analysis.dispatchers[&level].interrupt; - - quote!( - #(#cfgs)* - #enum_::#task => { - (#ready { #priority }).lock(|rq| { - rq.split().0.enqueue_unchecked((#tenum::#task, index)) - }); - - rtfm::pend(#device::Interrupt::#dispatcher); - } - ) - }) - .collect::<Vec<_>>(); + root.push(quote!( + #(#root_init)* - let logical_prio = analysis.timer_queue.priority; - let alias = ctxt.ident_gen.mk_ident(None, false); - items.push(quote!( - #[export_name = "SysTick"] - #[doc(hidden)] - unsafe fn #alias() { - use rtfm::Mutex; - - let ref #priority = rtfm::export::Priority::new(#logical_prio); - - rtfm::export::run(|| { - rtfm::export::sys_tick(#tq { #priority }, |task, index| { - match task { - #(#arms)* - } - }); - }) - } + #(#root_idle)* )); - quote!(#(#items)*) -} - -fn pre_init(ctxt: &Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream { - let mut exprs = vec![]; - - if !cfg!(feature = "nightly") { - // these are `MaybeUninit` arrays - for task in ctxt.tasks.values() { - let inputs = &task.inputs; - exprs.push(quote!(#inputs.write(core::mem::uninitialized());)) - } - - #[cfg(feature = "timer-queue")] - for task in ctxt.tasks.values() { - let scheduleds = &task.scheduleds; - exprs.push(quote!(#scheduleds.write(core::mem::uninitialized());)) - } + mod_app.push(quote!( + #mod_app_init - // these are `MaybeUninit` `ReadyQueue`s - for dispatcher in ctxt.dispatchers.values() { - let rq = &dispatcher.ready_queue; - exprs.push(quote!(#rq.write(rtfm::export::ReadyQueue::new_sc());)) - } - - // these are `MaybeUninit` `FreeQueue`s - for task in ctxt.tasks.values() { - let fq = &task.free_queue; - exprs.push(quote!(#fq.write(rtfm::export::FreeQueue::new_sc());)) - } - } - - // Initialize the timer queue - if !analysis.timer_queue.tasks.is_empty() { - let tq = &ctxt.timer_queue; - exprs.push(quote!(#tq.write(rtfm::export::TimerQueue::new(p.SYST));)); - } - - // Populate the `FreeQueue`s - for (name, task) in &ctxt.tasks { - let fq = &task.free_queue; - let fq_ = if cfg!(feature = "nightly") { - quote!(#fq) - } else { - quote!(#fq.get_mut()) - }; - let capacity = analysis.capacities[name]; - exprs.push(quote!( - for i in 0..#capacity { - #fq_.enqueue_unchecked(i); - } - )) - } - - let device = &app.args.device; - let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS); - for (handler, interrupt) in &app.interrupts { - let name = interrupt.args.binds(handler); - let priority = interrupt.args.priority; - exprs.push(quote!(p.NVIC.enable(#device::Interrupt::#name);)); - exprs.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];)); - exprs.push(quote!(p.NVIC.set_priority( - #device::Interrupt::#name, - ((1 << #nvic_prio_bits) - #priority) << (8 - #nvic_prio_bits), - );)); - } - - for (priority, dispatcher) in &analysis.dispatchers { - let name = &dispatcher.interrupt; - exprs.push(quote!(p.NVIC.enable(#device::Interrupt::#name);)); - exprs.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];)); - exprs.push(quote!(p.NVIC.set_priority( - #device::Interrupt::#name, - ((1 << #nvic_prio_bits) - #priority) << (8 - #nvic_prio_bits), - );)); - } - - // Set the cycle count to 0 and disable it while `init` executes - if cfg!(feature = "timer-queue") { - exprs.push(quote!(p.DWT.ctrl.modify(|r| r & !1);)); - exprs.push(quote!(p.DWT.cyccnt.write(0);)); - } - - quote!( - let mut p = rtfm::export::Peripherals::steal(); - #(#exprs)* - ) -} - -fn assertions(app: &App, analysis: &Analysis) -> proc_macro2::TokenStream { - let mut items = vec![]; - - for ty in &analysis.assert_sync { - items.push(quote!(rtfm::export::assert_sync::<#ty>())); - } - - for task in &analysis.tasks_assert_send { - let ty = tuple_ty(&app.tasks[task].inputs); - items.push(quote!(rtfm::export::assert_send::<#ty>())); - } - - // all late resources need to be `Send` - for ty in &analysis.resources_assert_send { - items.push(quote!(rtfm::export::assert_send::<#ty>())); - } - - quote!(#(#items;)*) -} - -fn mk_resource( - ctxt: &Context, - cfgs: &[Attribute], - struct_: &Ident, - ty: proc_macro2::TokenStream, - ceiling: u8, - ptr: proc_macro2::TokenStream, - app: &App, - module: Option<&mut Vec<proc_macro2::TokenStream>>, -) -> proc_macro2::TokenStream { - let priority = &ctxt.priority; - let device = &app.args.device; + #mod_app_idle + )); - let mut items = vec![]; + let main = util::suffixed("main"); + mains.push(quote!( + #[no_mangle] + unsafe extern "C" fn #main() -> ! { + let _TODO: () = (); - let path = if let Some(module) = module { - let doc = format!("`{}`", ty); - module.push(quote!( - #[allow(non_camel_case_types)] - #[doc = #doc] - #(#cfgs)* - pub struct #struct_<'a> { - #[doc(hidden)] - pub #priority: &'a rtfm::export::Priority, - } - )); + #(#assertion_stmts)* - quote!(resources::#struct_) - } else { - items.push(quote!( - #(#cfgs)* - struct #struct_<'a> { - #priority: &'a rtfm::export::Priority, - } - )); + #(#pre_init_stmts)* - quote!(#struct_) - }; + #call_init - items.push(quote!( - #(#cfgs)* - impl<'a> rtfm::Mutex for #path<'a> { - type T = #ty; + #(#post_init_stmts)* - #[inline] - fn lock<R, F>(&mut self, f: F) -> R - where - F: FnOnce(&mut Self::T) -> R, - { - unsafe { - rtfm::export::claim( - #ptr, - &self.#priority, - #ceiling, - #device::NVIC_PRIO_BITS, - f, - ) - } - } + #call_idle } )); - quote!(#(#items)*) -} + let (mod_app_resources, mod_resources, mod_resources_imports) = + resources::codegen(app, analysis, extra); -fn mk_capacity_literal(capacity: u8) -> LitInt { - LitInt::new(u64::from(capacity), IntSuffix::None, Span::call_site()) -} + let ( + mod_app_hardware_tasks, + root_hardware_tasks, + user_hardware_tasks, + user_hardware_tasks_imports, + ) = hardware_tasks::codegen(app, analysis, extra); -fn mk_typenum_capacity(capacity: u8, power_of_two: bool) -> proc_macro2::TokenStream { - let capacity = if power_of_two { - capacity - .checked_next_power_of_two() - .expect("capacity.next_power_of_two()") - } else { - capacity - }; + let ( + mod_app_software_tasks, + root_software_tasks, + user_software_tasks, + user_software_tasks_imports, + ) = software_tasks::codegen(app, analysis, extra); - let ident = Ident::new(&format!("U{}", capacity), Span::call_site()); + let mod_app_dispatchers = dispatchers::codegen(app, analysis, extra); - quote!(rtfm::export::consts::#ident) -} + let mod_app_spawn = spawn::codegen(app, analysis, extra); -struct IdentGenerator { - call_count: u32, - rng: rand::rngs::SmallRng, -} + let mod_app_timer_queue = timer_queue::codegen(app, analysis, extra); -impl IdentGenerator { - fn new() -> IdentGenerator { - let elapsed = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + let mod_app_schedule = schedule::codegen(app, extra); - let secs = elapsed.as_secs(); - let nanos = elapsed.subsec_nanos(); + let user_imports = app.user_imports.clone(); + let user_code = app.user_code.clone(); + let name = &app.name; + let device = extra.device; + quote!( + #(#user)* - let mut seed: [u8; 16] = [0; 16]; + #(#user_hardware_tasks)* - for (i, v) in seed.iter_mut().take(8).enumerate() { - *v = ((secs >> (i * 8)) & 0xFF) as u8 - } + #(#user_software_tasks)* - for (i, v) in seed.iter_mut().skip(8).take(4).enumerate() { - *v = ((nanos >> (i * 8)) & 0xFF) as u8 - } + #(#root)* - let rng = rand::rngs::SmallRng::from_seed(seed); + #mod_resources - IdentGenerator { call_count: 0, rng } - } + #(#root_hardware_tasks)* - fn mk_ident(&mut self, name: Option<&str>, random: bool) -> Ident { - let s = if let Some(name) = name { - format!("{}_", name) - } else { - "__rtfm_internal_".to_string() - }; + #(#root_software_tasks)* - let mut s = format!("{}{}", s, self.call_count); - self.call_count += 1; + /// Implementation details + mod #name { + /// Always include the device crate which contains the vector table + use #device as _; + #(#imports)* + #(#user_imports)* - if random { - s.push('_'); + /// User code from within the module + #(#user_code)* + /// User code end - for i in 0..4 { - if i == 0 || self.rng.gen() { - s.push(('a' as u8 + self.rng.gen::<u8>() % 25) as char) - } else { - s.push(('0' as u8 + self.rng.gen::<u8>() % 10) as char) - } - } - } - Ident::new(&s, Span::call_site()) - } -} - -// `once = true` means that these locals will be called from a function that will run *once* -fn mk_locals(locals: &BTreeMap<Ident, Static>, once: bool) -> proc_macro2::TokenStream { - let lt = if once { Some(quote!('static)) } else { None }; - - let locals = locals - .iter() - .map(|(name, static_)| { - let attrs = &static_.attrs; - let cfgs = &static_.cfgs; - let expr = &static_.expr; - let ident = name; - let ty = &static_.ty; + #(#user_hardware_tasks_imports)* - quote!( - #[allow(non_snake_case)] - #(#cfgs)* - let #ident: &#lt mut #ty = { - #(#attrs)* - #(#cfgs)* - static mut #ident: #ty = #expr; + #(#user_software_tasks_imports)* - unsafe { &mut #ident } - }; - ) - }) - .collect::<Vec<_>>(); - - quote!(#(#locals)*) -} + #(#mod_resources_imports)* -fn tuple_pat(inputs: &[ArgCaptured]) -> proc_macro2::TokenStream { - if inputs.len() == 1 { - let pat = &inputs[0].pat; - quote!(#pat) - } else { - let pats = inputs.iter().map(|i| &i.pat).collect::<Vec<_>>(); + /// app module + #(#mod_app)* - quote!(#(#pats,)*) - } -} + #(#mod_app_resources)* -fn tuple_ty(inputs: &[ArgCaptured]) -> proc_macro2::TokenStream { - if inputs.len() == 1 { - let ty = &inputs[0].ty; - quote!(#ty) - } else { - let tys = inputs.iter().map(|i| &i.ty).collect::<Vec<_>>(); + #(#mod_app_hardware_tasks)* - quote!((#(#tys,)*)) - } -} + #(#mod_app_software_tasks)* -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -enum Kind { - Exception(Ident), - Idle, - Init, - Interrupt(Ident), - Task(Ident), -} + #(#mod_app_dispatchers)* -impl Kind { - fn ident(&self) -> Ident { - match self { - Kind::Init => Ident::new("init", Span::call_site()), - Kind::Idle => Ident::new("idle", Span::call_site()), - Kind::Task(name) | Kind::Interrupt(name) | Kind::Exception(name) => name.clone(), - } - } + #(#mod_app_spawn)* - fn is_idle(&self) -> bool { - *self == Kind::Idle - } + #(#mod_app_timer_queue)* - fn is_init(&self) -> bool { - *self == Kind::Init - } + #(#mod_app_schedule)* - fn runs_once(&self) -> bool { - match *self { - Kind::Init | Kind::Idle => true, - _ => false, + #(#mains)* } - } + ) } |