diff options
Diffstat (limited to 'macros/src/codegen.rs')
-rw-r--r-- | macros/src/codegen.rs | 2467 |
1 files changed, 87 insertions, 2380 deletions
diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs index 88f11739..86b4a67e 100644 --- a/macros/src/codegen.rs +++ b/macros/src/codegen.rs @@ -1,136 +1,75 @@ -use proc_macro::TokenStream; -use std::collections::{BTreeMap, BTreeSet}; - -use proc_macro2::Span; +use proc_macro2::TokenStream as TokenStream2; use quote::quote; -use syn::{ArgCaptured, Attribute, Ident, IntSuffix, LitInt}; - -use crate::{ - analyze::{Analysis, Ownership}, - syntax::{App, Static}, -}; - -pub fn app(name: &Ident, app: &App, analysis: &Analysis) -> TokenStream { - let (const_app_resources, mod_resources) = resources(app, analysis); - - let ( - const_app_exceptions, - exception_mods, - exception_locals, - exception_resources, - user_exceptions, - ) = exceptions(app, analysis); - - let ( - const_app_interrupts, - interrupt_mods, - interrupt_locals, - interrupt_resources, - user_interrupts, - ) = interrupts(app, analysis); - - let (const_app_tasks, task_mods, task_locals, task_resources, user_tasks) = - tasks(app, analysis); - - let const_app_dispatchers = dispatchers(&app, analysis); - - let const_app_spawn = spawn(app, analysis); - - let const_app_tq = timer_queue(app, analysis); - - let const_app_schedule = schedule(app); - - let assertion_stmts = assertions(app, analysis); - - let pre_init_stmts = pre_init(&app, analysis); - - let ( - const_app_init, - mod_init, - init_locals, - init_resources, - init_late_resources, - user_init, - call_init, - ) = init(app, analysis); - - let post_init_stmts = post_init(&app, analysis); - - let (const_app_idle, mod_idle, idle_locals, idle_resources, user_idle, call_idle) = - idle(app, analysis); - - let device = &app.args.device; - quote!( - #user_init - - #user_idle - - #(#user_exceptions)* - - #(#user_interrupts)* - - #(#user_tasks)* - - #mod_resources - - #init_locals - - #init_resources - - #init_late_resources - - #mod_init - - #idle_locals - - #idle_resources - - #mod_idle - - #(#exception_locals)* +use rtfm_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 `rtfm-syntax` +pub fn app(app: &App, analysis: &Analysis, extra: &Extra) -> TokenStream2 { + let mut const_app = vec![]; + let mut mains = vec![]; + let mut root = vec![]; + let mut user = vec![]; - #(#exception_resources)* + // generate a `main` function for each core + for core in 0..app.args.cores { + let assertion_stmts = assertions::codegen(core, analysis); - #(#exception_mods)* + let (const_app_pre_init, pre_init_stmts) = pre_init::codegen(core, &app, analysis, extra); - #(#interrupt_locals)* + let (const_app_init, root_init, user_init, call_init) = + init::codegen(core, app, analysis, extra); - #(#interrupt_resources)* + let (const_app_post_init, post_init_stmts) = post_init::codegen(core, analysis, extra); - #(#interrupt_mods)* + let (const_app_idle, root_idle, user_idle, call_idle) = + idle::codegen(core, app, analysis, extra); - #(#task_locals)* + user.push(quote!( + #user_init - #(#task_resources)* + #user_idle + )); - #(#task_mods)* + root.push(quote!( + #(#root_init)* - /// Implementation details - const #name: () = { - // always include the device crate, which contains the vector table - use #device as _; + #(#root_idle)* + )); - #(#const_app_resources)* + const_app.push(quote!( + #(#const_app_pre_init)* #const_app_init - #const_app_idle - - #(#const_app_exceptions)* - - #(#const_app_interrupts)* - - #(#const_app_dispatchers)* - - #(#const_app_tasks)* - - #(#const_app_spawn)* - - #(#const_app_tq)* + #(#const_app_post_init)* - #(#const_app_schedule)* + #const_app_idle + )); + let cfg_core = util::cfg_core(core, app.args.cores); + mains.push(quote!( #[no_mangle] + #cfg_core unsafe fn main() -> ! { #(#assertion_stmts)* @@ -142,2297 +81,65 @@ pub fn app(name: &Ident, app: &App, analysis: &Analysis) -> TokenStream { #call_idle } - }; - ) - .into() -} - -/* Main functions */ -/// In this pass we generate a static variable and a resource proxy for each resource -/// -/// If the user specified a resource like this: -/// -/// ``` -/// #[rtfm::app(device = ..)] -/// const APP: () = { -/// static mut X: UserDefinedStruct = (); -/// static mut Y: u64 = 0; -/// static mut Z: u32 = 0; -/// } -/// ``` -/// -/// We'll generate code like this: -/// -/// - `const_app` -/// -/// ``` -/// const APP: () = { -/// static mut X: MaybeUninit<UserDefinedStruct> = MaybeUninit::uninit(); -/// static mut Y: u64 = 0; -/// static mut Z: u32 = 0; -/// -/// impl<'a> Mutex for resources::X<'a> { .. } -/// -/// impl<'a> Mutex for resources::Y<'a> { .. } -/// -/// // but not for `Z` because it's not shared and thus requires no proxy -/// }; -/// ``` -/// -/// - `mod_resources` -/// -/// ``` -/// mod resources { -/// pub struct X<'a> { -/// priority: &'a Priority, -/// } -/// -/// impl<'a> X<'a> { -/// pub unsafe fn new(priority: &'a Priority) -> Self { -/// X { priority } -/// } -/// -/// pub unsafe fn priority(&self) -> &Priority { -/// self.priority -/// } -/// } -/// -/// // same thing for `Y` -/// -/// // but not for `Z` -/// } -/// ``` -fn resources( - app: &App, - analysis: &Analysis, -) -> ( - // const_app - Vec<proc_macro2::TokenStream>, - // mod_resources - proc_macro2::TokenStream, -) { - let mut const_app = vec![]; - let mut mod_resources = vec![]; - - for (name, res) in &app.resources { - let cfgs = &res.cfgs; - let attrs = &res.attrs; - let ty = &res.ty; - - if let Some(expr) = res.expr.as_ref() { - const_app.push(quote!( - #(#attrs)* - #(#cfgs)* - static mut #name: #ty = #expr; - )); - } else { - const_app.push(quote!( - #(#attrs)* - #(#cfgs)* - static mut #name: core::mem::MaybeUninit<#ty> = - core::mem::MaybeUninit::uninit(); - )); - } - - // generate a resource proxy when needed - if res.mutability.is_some() { - if let Some(Ownership::Shared { ceiling }) = analysis.ownerships.get(name) { - let ptr = if res.expr.is_none() { - quote!(#name.as_mut_ptr()) - } else { - quote!(&mut #name) - }; - - mod_resources.push(quote!( - pub struct #name<'a> { - priority: &'a Priority, - } - - 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 - } - } - )); - - const_app.push(impl_mutex( - app, - cfgs, - true, - name, - quote!(#ty), - *ceiling, - ptr, - )); - } - } - } - - let mod_resources = if mod_resources.is_empty() { - quote!() - } else { - quote!(mod resources { - use rtfm::export::Priority; - - #(#mod_resources)* - }) - }; - - (const_app, mod_resources) -} - -// For each exception we'll generate: -// -// - at the root of the crate: -// - a ${name}Resources struct (maybe) -// - a ${name}Locals struct -// -// - a module named after the exception, see the `module` function for more details -// -// - hidden in `const APP` -// - the ${name}Resources constructor -// -// - the exception handler specified by the user -fn exceptions( - app: &App, - analysis: &Analysis, -) -> ( - // const_app - Vec<proc_macro2::TokenStream>, - // exception_mods - Vec<proc_macro2::TokenStream>, - // exception_locals - Vec<proc_macro2::TokenStream>, - // exception_resources - Vec<proc_macro2::TokenStream>, - // user_exceptions - Vec<proc_macro2::TokenStream>, -) { - let mut const_app = vec![]; - let mut mods = vec![]; - let mut locals_structs = vec![]; - let mut resources_structs = vec![]; - let mut user_code = vec![]; - - for (name, exception) in &app.exceptions { - let (let_instant, instant) = if cfg!(feature = "timer-queue") { - ( - Some(quote!(let instant = rtfm::Instant::now();)), - Some(quote!(, instant)), - ) - } else { - (None, None) - }; - let priority = &exception.args.priority; - let symbol = exception.args.binds(name); - const_app.push(quote!( - #[allow(non_snake_case)] - #[no_mangle] - unsafe fn #symbol() { - const PRIORITY: u8 = #priority; - - #let_instant - - rtfm::export::run(PRIORITY, || { - crate::#name( - #name::Locals::new(), - #name::Context::new(&rtfm::export::Priority::new(PRIORITY) #instant) - ) - }); - } - )); - - let mut needs_lt = false; - if !exception.args.resources.is_empty() { - let (item, constructor) = resources_struct( - Kind::Exception(name.clone()), - exception.args.priority, - &mut needs_lt, - app, - analysis, - ); - - resources_structs.push(item); - - const_app.push(constructor); - } - - mods.push(module( - Kind::Exception(name.clone()), - (!exception.args.resources.is_empty(), needs_lt), - !exception.args.schedule.is_empty(), - !exception.args.spawn.is_empty(), - false, - app, - )); - - let attrs = &exception.attrs; - let context = &exception.context; - let (locals, lets) = locals(Kind::Exception(name.clone()), &exception.statics); - locals_structs.push(locals); - let use_u32ext = if cfg!(feature = "timer-queue") { - Some(quote!( - use rtfm::U32Ext as _; - )) - } else { - None - }; - let stmts = &exception.stmts; - user_code.push(quote!( - #(#attrs)* - #[allow(non_snake_case)] - fn #name(__locals: #name::Locals, #context: #name::Context) { - #use_u32ext - use rtfm::Mutex as _; - - #(#lets;)* - - #(#stmts)* - } - )); - } - - ( - const_app, - mods, - locals_structs, - resources_structs, - user_code, - ) -} - -// For each interrupt we'll generate: -// -// - at the root of the crate: -// - a ${name}Resources struct (maybe) -// - a ${name}Locals struct -// -// - a module named after the exception, see the `module` function for more details -// -// - hidden in `const APP` -// - the ${name}Resources constructor -// -// - the interrupt handler specified by the user -fn interrupts( - app: &App, - analysis: &Analysis, -) -> ( - // const_app - Vec<proc_macro2::TokenStream>, - // interrupt_mods - Vec<proc_macro2::TokenStream>, - // interrupt_locals - Vec<proc_macro2::TokenStream>, - // interrupt_resources - Vec<proc_macro2::TokenStream>, - // user_exceptions - Vec<proc_macro2::TokenStream>, -) { - let mut const_app = vec![]; - let mut mods = vec![]; - let mut locals_structs = vec![]; - let mut resources_structs = vec![]; - let mut user_code = vec![]; - - let device = &app.args.device; - for (name, interrupt) in &app.interrupts { - let (let_instant, instant) = if cfg!(feature = "timer-queue") { - ( - Some(quote!(let instant = rtfm::Instant::now();)), - Some(quote!(, instant)), - ) - } else { - (None, None) - }; - let priority = &interrupt.args.priority; - let symbol = interrupt.args.binds(name); - const_app.push(quote!( - #[allow(non_snake_case)] - #[no_mangle] - unsafe fn #symbol() { - const PRIORITY: u8 = #priority; - - #let_instant - - // check that this interrupt exists - let _ = #device::Interrupt::#symbol; - - rtfm::export::run(PRIORITY, || { - crate::#name( - #name::Locals::new(), - #name::Context::new(&rtfm::export::Priority::new(PRIORITY) #instant) - ) - }); - } - )); - - let mut needs_lt = false; - if !interrupt.args.resources.is_empty() { - let (item, constructor) = resources_struct( - Kind::Interrupt(name.clone()), - interrupt.args.priority, - &mut needs_lt, - app, - analysis, - ); - - resources_structs.push(item); - - const_app.push(constructor); - } - - mods.push(module( - Kind::Interrupt(name.clone()), - (!interrupt.args.resources.is_empty(), needs_lt), - !interrupt.args.schedule.is_empty(), - !interrupt.args.spawn.is_empty(), - false, - app, - )); - - let attrs = &interrupt.attrs; - let context = &interrupt.context; - let use_u32ext = if cfg!(feature = "timer-queue") { - Some(quote!( - use rtfm::U32Ext as _; - )) - } else { - None - }; - let (locals, lets) = locals(Kind::Interrupt(name.clone()), &interrupt.statics); - locals_structs.push(locals); - let stmts = &interrupt.stmts; - user_code.push(quote!( - #(#attrs)* - #[allow(non_snake_case)] - fn #name(__locals: #name::Locals, #context: #name::Context) { - #use_u32ext - use rtfm::Mutex as _; - - #(#lets;)* - - #(#stmts)* - } - )); - } - - ( - const_app, - mods, - locals_structs, - resources_structs, - user_code, - ) -} - -// For each task we'll generate: -// -// - at the root of the crate: -// - a ${name}Resources struct (maybe) -// - a ${name}Locals struct -// -// - a module named after the task, see the `module` function for more details -// -// - hidden in `const APP` -// - the ${name}Resources constructor -// - an INPUTS buffer -// - a free queue and a corresponding resource -// - an INSTANTS buffer (if `timer-queue` is enabled) -// -// - the task handler specified by the user -fn tasks( - app: &App, - analysis: &Analysis, -) -> ( - // const_app - Vec<proc_macro2::TokenStream>, - // task_mods - Vec<proc_macro2::TokenStream>, - // task_locals - Vec<proc_macro2::TokenStream>, - // task_resources - Vec<proc_macro2::TokenStream>, - // user_tasks - Vec<proc_macro2::TokenStream>, -) { - let mut const_app = vec![]; - let mut mods = vec![]; - let mut locals_structs = vec![]; - let mut resources_structs = vec![]; - let mut user_code = vec![]; - - for (name, task) in &app.tasks { - let inputs = &task.inputs; - let (_, _, _, ty) = regroup_inputs(inputs); - - let cap = analysis.capacities[name]; - let cap_lit = mk_capacity_literal(cap); - let cap_ty = mk_typenum_capacity(cap, true); - - let task_inputs = mk_inputs_ident(name); - let task_instants = mk_instants_ident(name); - let task_fq = mk_fq_ident(name); - - let elems = (0..cap) - .map(|_| quote!(core::mem::MaybeUninit::uninit())) - .collect::<Vec<_>>(); - - if cfg!(feature = "timer-queue") { - let elems = elems.clone(); - const_app.push(quote!( - /// Buffer that holds the instants associated to the inputs of a task - static mut #task_instants: [core::mem::MaybeUninit<rtfm::Instant>; #cap_lit] = - [#(#elems,)*]; - )); - } - - const_app.push(quote!( - /// Buffer that holds the inputs of a task - static mut #task_inputs: [core::mem::MaybeUninit<#ty>; #cap_lit] = - [#(#elems,)*]; - )); - - let doc = "Queue version of a free-list that keeps track of empty slots in the previous buffer(s)"; - let fq_ty = quote!(rtfm::export::FreeQueue<#cap_ty>); - const_app.push(quote!( - #[doc = #doc] - static mut #task_fq: #fq_ty = unsafe { - rtfm::export::Queue(rtfm::export::i::Queue::u8_sc()) - }; - )); - let ptr = quote!(&mut #task_fq); - - if let Some(ceiling) = analysis.free_queues.get(name) { - const_app.push(quote!(struct #task_fq<'a> { - priority: &'a rtfm::export::Priority, - })); - - const_app.push(impl_mutex(app, &[], false, &task_fq, fq_ty, *ceiling, ptr)); - } - - let mut needs_lt = false; - if !task.args.resources.is_empty() { - let (item, constructor) = resources_struct( - Kind::Task(name.clone()), - task.args.priority, - &mut needs_lt, - app, - analysis, - ); - - resources_structs.push(item); - - const_app.push(constructor); - } - - mods.push(module( - Kind::Task(name.clone()), - (!task.args.resources.is_empty(), needs_lt), - !task.args.schedule.is_empty(), - !task.args.spawn.is_empty(), - false, - app, - )); - - let attrs = &task.attrs; - let use_u32ext = if cfg!(feature = "timer-queue") { - Some(quote!( - use rtfm::U32Ext as _; - )) - } else { - None - }; - let context = &task.context; - let stmts = &task.stmts; - let (locals_struct, lets) = locals(Kind::Task(name.clone()), &task.statics); - locals_structs.push(locals_struct); - user_code.push(quote!( - #(#attrs)* - #[allow(non_snake_case)] - fn #name(__locals: #name::Locals, #context: #name::Context #(,#inputs)*) { - use rtfm::Mutex as _; - #use_u32ext - - #(#lets;)* - - #(#stmts)* - } - )); - } - - ( - const_app, - mods, - locals_structs, - resources_structs, - user_code, - ) -} - -/// For each task dispatcher we'll generate -/// -/// - A static variable that hold the ready queue (`RQ${priority}`) and a resource proxy for it -/// - An enumeration of all the tasks dispatched by this dispatcher `T${priority}` -/// - An interrupt handler that dispatches the tasks -fn dispatchers(app: &App, analysis: &Analysis) -> Vec<proc_macro2::TokenStream> { - let mut items = vec![]; - - let device = &app.args.device; - for (level, dispatcher) in &analysis.dispatchers { - let rq = mk_rq_ident(*level); - let t = mk_t_ident(*level); - let cap = mk_typenum_capacity(dispatcher.capacity, true); - - let doc = format!( - "Queue of tasks ready to be dispatched at priority level {}", - level - ); - let rq_ty = quote!(rtfm::export::ReadyQueue<#t, #cap>); - items.push(quote!( - #[doc = #doc] - static mut #rq: #rq_ty = unsafe { - rtfm::export::Queue(rtfm::export::i::Queue::u8_sc()) - }; - )); - let ptr = quote!(&mut #rq); - - if let Some(ceiling) = analysis.ready_queues.get(&level) { - items.push(quote!( - struct #rq<'a> { - priority: &'a rtfm::export::Priority, - } - )); - - items.push(impl_mutex(app, &[], false, &rq, rq_ty, *ceiling, ptr)); - } - - let variants = dispatcher - .tasks - .iter() - .map(|task| { - let cfgs = &app.tasks[task].cfgs; - - quote!( - #(#cfgs)* - #task - ) - }) - .collect::<Vec<_>>(); - - let doc = format!( - "Software tasks to be dispatched at priority level {}", - level - ); - items.push(quote!( - #[allow(non_camel_case_types)] - #[derive(Clone, Copy)] - #[doc = #doc] - enum #t { - #(#variants,)* - } - )); - - let arms = dispatcher - .tasks - .iter() - .map(|name| { - let task = &app.tasks[name]; - let cfgs = &task.cfgs; - let (_, tupled, pats, _) = regroup_inputs(&task.inputs); - - let inputs = mk_inputs_ident(name); - let fq = mk_fq_ident(name); - - let input = quote!(#inputs.get_unchecked(usize::from(index)).as_ptr().read()); - let fq = quote!(#fq); - - let (let_instant, _instant) = if cfg!(feature = "timer-queue") { - let instants = mk_instants_ident(name); - let instant = - quote!(#instants.get_unchecked(usize::from(index)).as_ptr().read()); - - ( - Some(quote!(let instant = #instant;)), - Some(quote!(, instant)), - ) - } else { - (None, None) - }; - - let call = { - let pats = pats.clone(); - - quote!( - #name( - #name::Locals::new(), - #name::Context::new(priority #_instant) - #(,#pats)* - ) - ) - }; - - quote!( - #(#cfgs)* - #t::#name => { - let #tupled = #input; - #let_instant - #fq.split().0.enqueue_unchecked(index); - let priority = &rtfm::export::Priority::new(PRIORITY); - #call - } - ) - }) - .collect::<Vec<_>>(); - - let doc = format!( - "interrupt handler used to dispatch tasks at priority {}", - level - ); - let attrs = &dispatcher.attrs; - let interrupt = &dispatcher.interrupt; - let rq = quote!((&mut #rq)); - items.push(quote!( - #[doc = #doc] - #(#attrs)* - #[no_mangle] - #[allow(non_snake_case)] - unsafe fn #interrupt() { - /// The priority of this interrupt handler - const PRIORITY: u8 = #level; - - // check that this interrupt exists - let _ = #device::Interrupt::#interrupt; - - rtfm::export::run(PRIORITY, || { - while let Some((task, index)) = #rq.split().1.dequeue() { - match task { - #(#arms)* - } - } - }); - } - )); - } - - items -} - -/// Generates all the `Spawn.$task` related code -fn spawn(app: &App, analysis: &Analysis) -> Vec<proc_macro2::TokenStream> { - let mut items = vec![]; - - let mut seen = BTreeSet::new(); - for (spawner, spawnees) in app.spawn_callers() { - if spawnees.is_empty() { - continue; - } - - let mut methods = vec![]; - - let spawner_is_init = spawner == "init"; - let spawner_is_idle = spawner == "idle"; - for name in spawnees { - let spawnee = &app.tasks[name]; - let cfgs = &spawnee.cfgs; - let (args, _, untupled, ty) = regroup_inputs(&spawnee.inputs); - - 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 = mk_spawn_body(&spawner, &name, app, analysis); - - let let_instant = if cfg!(feature = "timer-queue") { - Some(quote!(let instant = unsafe { rtfm::Instant::artificial(0) };)) - } else { - None - }; - methods.push(quote!( - #(#cfgs)* - fn #name(&self #(,#args)*) -> Result<(), #ty> { - #let_instant - #body - } - )); - } else { - let spawn = mk_spawn_ident(name); - - if !seen.contains(name) { - // generate a `spawn_${name}` function - seen.insert(name); - - let instant = if cfg!(feature = "timer-queue") { - Some(quote!(, instant: rtfm::Instant)) - } else { - None - }; - let body = mk_spawn_body(&spawner, &name, app, analysis); - let args = args.clone(); - items.push(quote!( - #(#cfgs)* - unsafe fn #spawn( - priority: &rtfm::export::Priority - #instant - #(,#args)* - ) -> Result<(), #ty> { - #body - } - )); - } - - let (let_instant, instant) = if cfg!(feature = "timer-queue") { - ( - Some(if spawner_is_idle { - quote!(let instant = rtfm::Instant::now();) - } else { - quote!(let instant = self.instant();) - }), - Some(quote!(, instant)), - ) - } else { - (None, None) - }; - methods.push(quote!( - #(#cfgs)* - #[inline(always)] - fn #name(&self #(,#args)*) -> Result<(), #ty> { - unsafe { - #let_instant - #spawn(self.priority() #instant #(,#untupled)*) - } - } - )); - } - } - - let lt = if spawner_is_init { - None - } else { - Some(quote!('a)) - }; - items.push(quote!( - impl<#lt> #spawner::Spawn<#lt> { - #(#methods)* - } - )); - } - - items -} - -/// Generates code related to the timer queue, namely -/// -/// - A static variable that holds the timer queue and a resource proxy for it -/// - The system timer exception, which moves tasks from the timer queue into the ready queues -fn timer_queue(app: &App, analysis: &Analysis) -> Vec<proc_macro2::TokenStream> { - let mut items = vec![]; - - let tasks = &analysis.timer_queue.tasks; - - if tasks.is_empty() { - return items; - } - - let variants = tasks - .iter() - .map(|task| { - let cfgs = &app.tasks[task].cfgs; - quote!( - #(#cfgs)* - #task - ) - }) - .collect::<Vec<_>>(); - - items.push(quote!( - /// `schedule`-dable tasks - #[allow(non_camel_case_types)] - #[derive(Clone, Copy)] - enum T { - #(#variants,)* - } - )); - - let cap = mk_typenum_capacity(analysis.timer_queue.capacity, false); - let ty = quote!(rtfm::export::TimerQueue<T, #cap>); - items.push(quote!( - /// The timer queue - static mut TQ: core::mem::MaybeUninit<#ty> = core::mem::MaybeUninit::uninit(); - )); - - items.push(quote!( - struct TQ<'a> { - priority: &'a rtfm::export::Priority, - } - )); - - items.push(impl_mutex( - app, - &[], - false, - &Ident::new("TQ", Span::call_site()), - ty, - analysis.timer_queue.ceiling, - quote!(TQ.as_mut_ptr()), - )); - - let device = &app.args.device; - let arms = tasks - .iter() - .map(|name| { - let task = &app.tasks[name]; - let cfgs = &task.cfgs; - let priority = task.args.priority; - let rq = mk_rq_ident(priority); - let t = mk_t_ident(priority); - let dispatcher = &analysis.dispatchers[&priority].interrupt; - - quote!( - #(#cfgs)* - T::#name => { - let priority = &rtfm::export::Priority::new(PRIORITY); - (#rq { priority }).lock(|rq| { - rq.split().0.enqueue_unchecked((#t::#name, index)) - }); - - rtfm::pend(#device::Interrupt::#dispatcher) - } - ) - }) - .collect::<Vec<_>>(); - - let priority = analysis.timer_queue.priority; - items.push(quote!( - /// The system timer - #[no_mangle] - unsafe fn SysTick() { - use rtfm::Mutex as _; - - /// System timer priority - const PRIORITY: u8 = #priority; - - rtfm::export::run(PRIORITY, || { - while let Some((task, index)) = (TQ { - // NOTE dynamic priority is always the static priority at this point - priority: &rtfm::export::Priority::new(PRIORITY), - }) - // NOTE `inline(always)` produces faster and smaller code - .lock(#[inline(always)] - |tq| tq.dequeue()) - { - match task { - #(#arms)* - } - } - }); - } - )); - - items -} - -/// Generates all the `Schedule.$task` related code -fn schedule(app: &App) -> Vec<proc_macro2::TokenStream> { - let mut items = vec![]; - if !cfg!(feature = "timer-queue") { - return items; - } - - let mut seen = BTreeSet::new(); - for (scheduler, schedulees) in app.schedule_callers() { - if schedulees.is_empty() { - continue; - } - - let mut methods = vec![]; - - let scheduler_is_init = scheduler == "init"; - for name in schedulees { - let schedulee = &app.tasks[name]; - - let (args, _, untupled, ty) = regroup_inputs(&schedulee.inputs); - - let cfgs = &schedulee.cfgs; - - let schedule = mk_schedule_ident(name); - if scheduler_is_init { - let body = mk_schedule_body(&scheduler, name, app); - - let args = args.clone(); - methods.push(quote!( - #(#cfgs)* - fn #name(&self, instant: rtfm::Instant #(,#args)*) -> Result<(), #ty> { - #body - } - )); - } else { - if !seen.contains(name) { - seen.insert(name); - - let body = mk_schedule_body(&scheduler, name, app); - let args = args.clone(); - - items.push(quote!( - #(#cfgs)* - fn #schedule( - priority: &rtfm::export::Priority, - instant: rtfm::Instant - #(,#args)* - ) -> Result<(), #ty> { - #body - } - )); - } - - methods.push(quote!( - #(#cfgs)* - #[inline(always)] - fn #name(&self, instant: rtfm::Instant #(,#args)*) -> Result<(), #ty> { - let priority = unsafe { self.priority() }; - - #schedule(priority, instant #(,#untupled)*) - } - )); - } - } - - let lt = if scheduler_is_init { - None - } else { - Some(quote!('a)) - }; - items.push(quote!( - impl<#lt> #scheduler::Schedule<#lt> { - #(#methods)* - } - )); - } - - items -} - -/// Generates `Send` / `Sync` compile time checks -fn assertions(app: &App, analysis: &Analysis) -> Vec<proc_macro2::TokenStream> { - let mut stmts = vec![]; - - for ty in &analysis.assert_sync { - stmts.push(quote!(rtfm::export::assert_sync::<#ty>();)); - } - - for task in &analysis.tasks_assert_send { - let (_, _, _, ty) = regroup_inputs(&app.tasks[task].inputs); - stmts.push(quote!(rtfm::export::assert_send::<#ty>();)); - } - - // all late resources need to be `Send` - for ty in &analysis.resources_assert_send { - stmts.push(quote!(rtfm::export::assert_send::<#ty>();)); - } - - stmts -} - -/// Generates code that we must run before `init` runs. See comments inside -fn pre_init(app: &App, analysis: &Analysis) -> Vec<proc_macro2::TokenStream> { - let mut stmts = vec![]; - - stmts.push(quote!(rtfm::export::interrupt::disable();)); - - // populate the `FreeQueue`s - for name in app.tasks.keys() { - let fq = mk_fq_ident(name); - let cap = analysis.capacities[name]; - - stmts.push(quote!( - for i in 0..#cap { - #fq.enqueue_unchecked(i); - } - )); - } - - stmts.push(quote!( - let mut core = rtfm::export::Peripherals::steal(); - )); - - // Initialize the timer queue - if !analysis.timer_queue.tasks.is_empty() { - stmts.push(quote!(TQ.as_mut_ptr().write(rtfm::export::TimerQueue::new(core.SYST));)); - } - - // set interrupts priorities - 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; - - stmts.push(quote!(core.NVIC.enable(#device::Interrupt::#name);)); - - // 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.NVIC.set_priority( - #device::Interrupt::#name, - rtfm::export::logical2hw(#priority, #nvic_prio_bits), - ); )); } - // set task dispatcher priorities - for (priority, dispatcher) in &analysis.dispatchers { - let name = &dispatcher.interrupt; - - stmts.push(quote!(core.NVIC.enable(#device::Interrupt::#name);)); - - // 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.NVIC.set_priority( - #device::Interrupt::#name, - rtfm::export::logical2hw(#priority, #nvic_prio_bits), - ); - )); - } - - // Set the cycle count to 0 and disable it while `init` executes - if cfg!(feature = "timer-queue") { - stmts.push(quote!(core.DWT.ctrl.modify(|r| r & !1);)); - stmts.push(quote!(core.DWT.cyccnt.write(0);)); - } - - stmts -} - -// This generates -// -// - at the root of the crate -// - a initResources struct (maybe) -// - a initLateResources struct (maybe) -// - a initLocals struct -// -// - an `init` module that contains -// - the `Context` struct -// - a re-export of the initResources struct -// - a re-export of the initLateResources struct -// - a re-export of the initLocals struct -// - the Spawn struct (maybe) -// - the Schedule struct (maybe, if `timer-queue` is enabled) -// -// - hidden in `const APP` -// - the initResources constructor -// -// - the user specified `init` function -// -// - a call to the user specified `init` function -fn init( - app: &App, - analysis: &Analysis, -) -> ( - // const_app - Option<proc_macro2::TokenStream>, - // mod_init - proc_macro2::TokenStream, - // init_locals - proc_macro2::TokenStream, - // init_resources - Option<proc_macro2::TokenStream>, - // init_late_resources - Option<proc_macro2::TokenStream>, - // user_init - proc_macro2::TokenStream, - // call_init - proc_macro2::TokenStream, -) { - let mut needs_lt = false; - let mut const_app = None; - let mut init_resources = None; - if !app.init.args.resources.is_empty() { - let (item, constructor) = resources_struct(Kind::Init, 0, &mut needs_lt, app, analysis); - - init_resources = Some(item); - const_app = Some(constructor); - } - - let core = if cfg!(feature = "timer-queue") { - quote!(rtfm::Peripherals { - CBP: core.CBP, - CPUID: core.CPUID, - DCB: &mut core.DCB, - FPB: core.FPB, - FPU: core.FPU, - ITM: core.ITM, - MPU: core.MPU, - SCB: &mut core.SCB, - TPIU: core.TPIU, - }) - } else { - quote!(rtfm::Peripherals { - CBP: core.CBP, - CPUID: core.CPUID, - DCB: core.DCB, - DWT: core.DWT, - FPB: core.FPB, - FPU: core.FPU, - ITM: core.ITM, - MPU: core.MPU, - SCB: &mut core.SCB, - SYST: core.SYST, - TPIU: core.TPIU, - }) - }; - - let call_init = quote!(let late = init(init::Locals::new(), init::Context::new(#core));); - - let late_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 attrs = &app.init.attrs; - let has_late_resources = !late_fields.is_empty(); - let (ret, init_late_resources) = if has_late_resources { - ( - Some(quote!(-> init::LateResources)), - Some(quote!( - /// Resources initialized at runtime - #[allow(non_snake_case)] - pub struct initLateResources { - #(#late_fields),* - } - )), - ) - } else { - (None, None) - }; - let context = &app.init.context; - let use_u32ext = if cfg!(feature = "timer-queue") { - Some(quote!( - use rtfm::U32Ext as _; - )) - } else { - None - }; - let (locals_struct, lets) = locals(Kind::Init, &app.init.statics); - let stmts = &app.init.stmts; - let user_init = quote!( - #(#attrs)* - #[allow(non_snake_case)] - fn init(__locals: init::Locals, #context: init::Context) #ret { - #use_u32ext - - #(#lets;)* - - #(#stmts)* - } - ); - - let mod_init = module( - Kind::Init, - (!app.init.args.resources.is_empty(), needs_lt), - !app.init.args.schedule.is_empty(), - !app.init.args.spawn.is_empty(), - has_late_resources, - app, - ); - - ( - const_app, - mod_init, - locals_struct, - init_resources, - init_late_resources, - user_init, - call_init, - ) -} - -/// Generates code that we must run after `init` returns. See comments inside -fn post_init(app: &App, analysis: &Analysis) -> Vec<proc_macro2::TokenStream> { - let mut stmts = vec![]; - - let device = &app.args.device; - let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS); - - // initialize late resources - for (name, res) in &app.resources { - if res.expr.is_some() { - continue; - } - - stmts.push(quote!(#name.as_mut_ptr().write(late.#name);)); - } - - // set exception priorities - for (handler, exception) in &app.exceptions { - let name = exception.args.binds(handler); - let priority = exception.args.priority; - - // compile time assert that this priority is supported by the device - stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];)); - - stmts.push(quote!(core.SCB.set_priority( - rtfm::export::SystemHandler::#name, - rtfm::export::logical2hw(#priority, #nvic_prio_bits), - );)); - } + let (const_app_resources, mod_resources) = resources::codegen(app, analysis, extra); - // set the system timer priority - if !analysis.timer_queue.tasks.is_empty() { - let priority = analysis.timer_queue.priority; + let (const_app_hardware_tasks, root_hardware_tasks, user_hardware_tasks) = + hardware_tasks::codegen(app, analysis, extra); - // compile time assert that this priority is supported by the device - stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];)); + let (const_app_software_tasks, root_software_tasks, user_software_tasks) = + software_tasks::codegen(app, analysis, extra); - stmts.push(quote!(core.SCB.set_priority( - rtfm::export::SystemHandler::SysTick, - rtfm::export::logical2hw(#priority, #nvic_prio_bits), - );)); - } + let const_app_dispatchers = dispatchers::codegen(app, analysis, extra); - if app.idle.is_none() { - // Set SLEEPONEXIT bit to enter sleep mode when returning from ISR - stmts.push(quote!(core.SCB.scr.modify(|r| r | 1 << 1);)); - } + let const_app_spawn = spawn::codegen(app, analysis, extra); - // enable and start the system timer - if !analysis.timer_queue.tasks.is_empty() { - stmts.push(quote!((*TQ.as_mut_ptr()) - .syst - .set_clock_source(rtfm::export::SystClkSource::Core);)); - stmts.push(quote!((*TQ.as_mut_ptr()).syst.enable_counter();)); - } - - // enable the cycle counter - if cfg!(feature = "timer-queue") { - stmts.push(quote!(core.DCB.enable_trace();)); - stmts.push(quote!(core.DWT.enable_cycle_counter();)); - } - - stmts.push(quote!(rtfm::export::interrupt::enable();)); - - stmts -} - -// If the user specified `idle` this generates -// -// - at the root of the crate -// - an idleResources struct (maybe) -// - an idleLocals struct -// -// - an `init` module that contains -// - the `Context` struct -// - a re-export of the idleResources struct -// - a re-export of the idleLocals struct -// - the Spawn struct (maybe) -// - the Schedule struct (maybe, if `timer-queue` is enabled) -// -// - hidden in `const APP` -// - the idleResources constructor -// -// - the user specified `idle` function -// -// - a call to the user specified `idle` function -// -// Otherwise it uses `loop { WFI }` as `idle` -fn idle( - app: &App, - analysis: &Analysis, -) -> ( - // const_app_idle - Option<proc_macro2::TokenStream>, - // mod_idle - Option<proc_macro2::TokenStream>, - // idle_locals - Option<proc_macro2::TokenStream>, - // idle_resources - Option<proc_macro2::TokenStream>, - // user_idle - Option<proc_macro2::TokenStream>, - // call_idle - proc_macro2::TokenStream, -) { - if let Some(idle) = app.idle.as_ref() { - let mut needs_lt = false; - let mut const_app = None; - let mut idle_resources = None; - - if !idle.args.resources.is_empty() { - let (item, constructor) = resources_struct(Kind::Idle, 0, &mut needs_lt, app, analysis); - - idle_resources = Some(item); - const_app = Some(constructor); - } - - let call_idle = quote!(idle( - idle::Locals::new(), - idle::Context::new(&rtfm::export::Priority::new(0)) - )); - - let attrs = &idle.attrs; - let context = &idle.context; - let use_u32ext = if cfg!(feature = "timer-queue") { - Some(quote!( - use rtfm::U32Ext as _; - )) - } else { - None - }; - let (idle_locals, lets) = locals(Kind::Idle, &idle.statics); - let stmts = &idle.stmts; - let user_idle = quote!( - #(#attrs)* - #[allow(non_snake_case)] - fn idle(__locals: idle::Locals, #context: idle::Context) -> ! { - #use_u32ext - use rtfm::Mutex as _; - - #(#lets;)* - - #(#stmts)* - } - ); + let const_app_timer_queue = timer_queue::codegen(app, analysis, extra); - let mod_idle = module( - Kind::Idle, - (!idle.args.resources.is_empty(), needs_lt), - !idle.args.schedule.is_empty(), - !idle.args.spawn.is_empty(), - false, - app, - ); + let const_app_schedule = schedule::codegen(app, extra); - ( - const_app, - Some(mod_idle), - Some(idle_locals), - idle_resources, - Some(user_idle), - call_idle, - ) - } else { - ( - None, - None, - None, - None, - None, - quote!(loop { - rtfm::export::wfi() - }), - ) - } -} - -/* Support functions */ -/// This function creates the `Resources` struct -/// -/// It's a bit unfortunate but this struct has to be created in the root because it refers to types -/// which may have been imported into the root. -fn resources_struct( - kind: Kind, - priority: u8, - needs_lt: &mut bool, - app: &App, - analysis: &Analysis, -) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { - let mut lt = None; - - let resources = match &kind { - Kind::Init => &app.init.args.resources, - Kind::Idle => &app.idle.as_ref().expect("UNREACHABLE").args.resources, - Kind::Interrupt(name) => &app.interrupts[name].args.resources, - Kind::Exception(name) => &app.exceptions[name].args.resources, - Kind::Task(name) => &app.tasks[name].args.resources, - }; - - let mut fields = vec![]; - let mut values = vec![]; - for name in resources { - let res = &app.resources[name]; - - let cfgs = &res.cfgs; - let mut_ = res.mutability; - let ty = &res.ty; - - if kind.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]; - - let mut exclusive = false; - 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 kind.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 { - exclusive = true; - - fields.push(quote!( - #(#cfgs)* - pub #name: rtfm::Exclusive<#lt, #ty> - )); - } - } - - let is_late = res.expr.is_none(); - if is_late { - let expr = if mut_.is_some() { - quote!(&mut *#name.as_mut_ptr()) - } else { - quote!(&*#name.as_ptr()) - }; - - if exclusive { - values.push(quote!( - #(#cfgs)* - #name: rtfm::Exclusive(#expr) - )); - } else { - values.push(quote!( - #(#cfgs)* - #name: #expr - )); - } - } else { - if exclusive { - values.push(quote!( - #(#cfgs)* - #name: rtfm::Exclusive(&mut #name) - )); - } else { - values.push(quote!( - #(#cfgs)* - #name: &#mut_ #name - )); - } - } - } - } - - if lt.is_some() { - *needs_lt = true; - - // the struct could end up empty due to `cfg` leading to an error due to `'a` being unused - fields.push(quote!( - #[doc(hidden)] - pub __marker__: core::marker::PhantomData<&'a ()> - )); - - values.push(quote!(__marker__: core::marker::PhantomData)) - } - - let ident = kind.resources_ident(); - let doc = format!("Resources {} has access to", ident); - let item = quote!( - #[allow(non_snake_case)] - #[doc = #doc] - pub struct #ident<#lt> { - #(#fields,)* - } - ); - let arg = if kind.is_init() { - None - } else { - Some(quote!(priority: &#lt rtfm::export::Priority)) - }; - let constructor = quote!( - impl<#lt> #ident<#lt> { - #[inline(always)] - unsafe fn new(#arg) -> Self { - #ident { - #(#values,)* - } - } - } - ); - (item, constructor) -} - -/// Creates a `Mutex` implementation -fn impl_mutex( - app: &App, - cfgs: &[Attribute], - resources_prefix: bool, - name: &Ident, - ty: proc_macro2::TokenStream, - ceiling: u8, - ptr: proc_macro2::TokenStream, -) -> proc_macro2::TokenStream { - let path = if resources_prefix { - quote!(resources::#name) - } else { - quote!(#name) - }; - - let priority = if resources_prefix { - quote!(self.priority()) - } else { - quote!(self.priority) - }; - - let device = &app.args.device; + let name = &app.name; + let device = extra.device; quote!( - #(#cfgs)* - impl<'a> rtfm::Mutex for #path<'a> { - type T = #ty; - - #[inline(always)] - fn lock<R>(&mut self, f: impl FnOnce(&mut #ty) -> R) -> R { - /// Priority ceiling - const CEILING: u8 = #ceiling; - - unsafe { - rtfm::export::lock( - #ptr, - #priority, - CEILING, - #device::NVIC_PRIO_BITS, - f, - ) - } - } - } - ) -} - -/// Creates a `Locals` struct and related code. This returns -/// -/// - `locals` -/// -/// ``` -/// pub struct Locals<'a> { -/// #[cfg(never)] -/// pub X: &'a mut X, -/// __marker__: PhantomData<&'a mut ()>, -/// } -/// ``` -/// -/// - `lt` -/// -/// ``` -/// 'a -/// ``` -/// -/// - `lets` -/// -/// ``` -/// #[cfg(never)] -/// let X = __locals.X -/// ``` -fn locals( - kind: Kind, - statics: &BTreeMap<Ident, Static>, -) -> ( - // locals - proc_macro2::TokenStream, - // lets - Vec<proc_macro2::TokenStream>, -) { - let runs_once = kind.runs_once(); - let ident = kind.locals_ident(); - - let mut lt = None; - let mut fields = vec![]; - let mut lets = vec![]; - let mut items = vec![]; - let mut values = vec![]; - for (name, static_) in statics { - let lt = if runs_once { - quote!('static) - } else { - lt = Some(quote!('a)); - quote!('a) - }; - - let cfgs = &static_.cfgs; - let expr = &static_.expr; - let ty = &static_.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 - )); - lets.push(quote!( - #(#cfgs)* - let #name = __locals.#name - )); - } - - if lt.is_some() { - fields.push(quote!(__marker__: core::marker::PhantomData<&'a mut ()>)); - values.push(quote!(__marker__: core::marker::PhantomData)); - } + #(#user)* - let locals = quote!( - #[allow(non_snake_case)] - #[doc(hidden)] - pub struct #ident<#lt> { - #(#fields),* - } + #(#user_hardware_tasks)* - impl<#lt> #ident<#lt> { - #[inline(always)] - unsafe fn new() -> Self { - #(#items;)* + #(#user_software_tasks)* - #ident { - #(#values),* - } - } - } - ); - - (locals, lets) -} + #(#root)* -/// This function creates a module that contains -// -// - the Context struct -// - a re-export of the ${name}Resources struct (maybe) -// - a re-export of the ${name}LateResources struct (maybe) -// - a re-export of the ${name}Locals struct -// - the Spawn struct (maybe) -// - the Schedule struct (maybe, if `timer-queue` is enabled) -fn module( - kind: Kind, - resources: (/* has */ bool, /* 'a */ bool), - schedule: bool, - spawn: bool, - late_resources: bool, - app: &App, -) -> proc_macro2::TokenStream { - let mut items = vec![]; - let mut fields = vec![]; - let mut values = vec![]; + #(#mod_resources)* - let name = kind.ident(); + #(#root_hardware_tasks)* - let mut needs_instant = false; - 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 - )); + #(#root_software_tasks)* - values.push(quote!(start: rtfm::Instant::artificial(0))); - } - - let device = &app.args.device; - fields.push(quote!( - /// Core (Cortex-M) peripherals - pub core: rtfm::Peripherals<'a> - )); - fields.push(quote!( - /// Device specific peripherals - pub device: #device::Peripherals - )); - - values.push(quote!(core)); - values.push(quote!(device: #device::Peripherals::steal())); - 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 - )); - - values.push(quote!(start: instant)); + /// Implementation details + // the user can't access the items within this `const` item + const #name: () = { + /// Always include the device crate which contains the vector table + use #device as _; - needs_instant = true; - } - } + #(#const_app)* - Kind::Task(_) => { - if cfg!(feature = "timer-queue") { - fields.push(quote!( - /// The time at which this task was scheduled to run - pub scheduled: rtfm::Instant - )); + #(#const_app_resources)* - values.push(quote!(scheduled: instant)); + #(#const_app_hardware_tasks)* - needs_instant = true; - } - } - } + #(#const_app_software_tasks)* - let ident = kind.locals_ident(); - items.push(quote!( - #[doc(inline)] - pub use super::#ident as Locals; - )); + #(#const_app_dispatchers)* - if resources.0 { - let ident = kind.resources_ident(); - let lt = if resources.1 { - lt = Some(quote!('a)); - Some(quote!('a)) - } else { - None - }; + #(#const_app_spawn)* - items.push(quote!( - #[doc(inline)] - pub use super::#ident as Resources; - )); + #(#const_app_timer_queue)* - fields.push(quote!( - /// Resources this task has access to - pub resources: Resources<#lt> - )); + #(#const_app_schedule)* - let priority = if kind.is_init() { - None - } else { - Some(quote!(priority)) + #(#mains)* }; - values.push(quote!(resources: Resources::new(#priority))); - } - - if schedule { - let doc = "Tasks that can be `schedule`-d from this context"; - if kind.is_init() { - items.push(quote!( - #[doc = #doc] - #[derive(Clone, Copy)] - pub struct Schedule { - _not_send: core::marker::PhantomData<*mut ()>, - } - )); - - fields.push(quote!( - #[doc = #doc] - pub schedule: Schedule - )); - - values.push(quote!( - schedule: Schedule { _not_send: core::marker::PhantomData } - )); - } else { - lt = Some(quote!('a)); - - items.push(quote!( - #[doc = #doc] - #[derive(Clone, Copy)] - pub struct Schedule<'a> { - priority: &'a rtfm::export::Priority, - } - - impl<'a> Schedule<'a> { - #[doc(hidden)] - #[inline(always)] - pub unsafe fn priority(&self) -> &rtfm::export::Priority { - &self.priority - } - } - )); - - fields.push(quote!( - #[doc = #doc] - pub schedule: Schedule<'a> - )); - - values.push(quote!( - schedule: Schedule { priority } - )); - } - } - - if spawn { - let doc = "Tasks that can be `spawn`-ed from this context"; - if kind.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 kind.is_idle() { - items.push(quote!( - #[doc = #doc] - #[derive(Clone, Copy)] - pub struct Spawn<'a> { - priority: &'a rtfm::export::Priority, - } - )); - - values.push(quote!(spawn: Spawn { priority })); - } else { - let instant_field = if cfg!(feature = "timer-queue") { - needs_instant = true; - instant_method = Some(quote!( - pub unsafe fn instant(&self) -> rtfm::Instant { - self.instant - } - )); - Some(quote!(instant: rtfm::Instant,)) - } else { - None - }; - - items.push(quote!( - /// Tasks that can be spawned from this context - #[derive(Clone, Copy)] - pub struct Spawn<'a> { - #instant_field - priority: &'a rtfm::export::Priority, - } - )); - - let _instant = if needs_instant { - Some(quote!(, instant)) - } else { - None - }; - values.push(quote!( - spawn: Spawn { priority #_instant } - )); - } - - items.push(quote!( - impl<'a> Spawn<'a> { - #[doc(hidden)] - #[inline(always)] - pub unsafe fn priority(&self) -> &rtfm::export::Priority { - self.priority - } - - #instant_method - } - )); - } - } - - if late_resources { - items.push(quote!( - #[doc(inline)] - pub use super::initLateResources as LateResources; - )); - } - - let doc = match kind { - Kind::Exception(_) => "Hardware task (exception)", - Kind::Idle => "Idle loop", - Kind::Init => "Initialization function", - Kind::Interrupt(_) => "Hardware task (interrupt)", - Kind::Task(_) => "Software task", - }; - - let core = if kind.is_init() { - lt = Some(quote!('a)); - Some(quote!(core: rtfm::Peripherals<'a>,)) - } else { - None - }; - - let priority = if kind.is_init() { - None - } else { - Some(quote!(priority: &#lt rtfm::export::Priority)) - }; - - let instant = if needs_instant { - Some(quote!(, instant: rtfm::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!() - } -} - -/// Creates the body of `spawn_${name}` -fn mk_spawn_body<'a>( - spawner: &Ident, - name: &Ident, - app: &'a App, - analysis: &Analysis, -) -> proc_macro2::TokenStream { - let spawner_is_init = spawner == "init"; - let device = &app.args.device; - - let spawnee = &app.tasks[name]; - let priority = spawnee.args.priority; - let dispatcher = &analysis.dispatchers[&priority].interrupt; - - let (_, tupled, _, _) = regroup_inputs(&spawnee.inputs); - - let inputs = mk_inputs_ident(name); - let fq = mk_fq_ident(name); - - let rq = mk_rq_ident(priority); - let t = mk_t_ident(priority); - - let write_instant = if cfg!(feature = "timer-queue") { - let instants = mk_instants_ident(name); - - Some(quote!( - #instants.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(instant); - )) - } else { - None - }; - - let (dequeue, enqueue) = if spawner_is_init { - // `init` has exclusive access to these queues so we can bypass the resources AND - // the consumer / producer split - ( - 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)) - });), - ) - }; - - quote!( - unsafe { - use rtfm::Mutex as _; - - let input = #tupled; - if let Some(index) = #dequeue { - #inputs.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(input); - - #write_instant - - #enqueue - - rtfm::pend(#device::Interrupt::#dispatcher); - - Ok(()) - } else { - Err(input) - } - } ) } - -/// Creates the body of `schedule_${name}` -fn mk_schedule_body<'a>(scheduler: &Ident, name: &Ident, app: &'a App) -> proc_macro2::TokenStream { - let scheduler_is_init = scheduler == "init"; - - let schedulee = &app.tasks[name]; - - let (_, tupled, _, _) = regroup_inputs(&schedulee.inputs); - - let fq = mk_fq_ident(name); - let inputs = mk_inputs_ident(name); - let instants = mk_instants_ident(name); - - let (dequeue, enqueue) = if scheduler_is_init { - // `init` has exclusive access to these queues so we can bypass the resources AND - // the consumer / producer split - let dequeue = quote!(#fq.dequeue()); - - (dequeue, quote!((*TQ.as_mut_ptr()).enqueue_unchecked(nr);)) - } else { - ( - quote!((#fq { priority }).lock(|fq| fq.split().1.dequeue())), - quote!((TQ { priority }).lock(|tq| tq.enqueue_unchecked(nr));), - ) - }; - - quote!( - unsafe { - use rtfm::Mutex as _; - - let input = #tupled; - if let Some(index) = #dequeue { - #instants.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(instant); - - #inputs.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(input); - - let nr = rtfm::export::NotReady { - instant, - index, - task: T::#name, - }; - - #enqueue - - Ok(()) - } else { - Err(input) - } - } - ) -} - -/// `u8` -> (unsuffixed) `LitInt` -fn mk_capacity_literal(capacity: u8) -> LitInt { - LitInt::new(u64::from(capacity), IntSuffix::None, Span::call_site()) -} - -/// e.g. `4u8` -> `U4` -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 ident = Ident::new(&format!("U{}", capacity), Span::call_site()); - - quote!(rtfm::export::consts::#ident) -} - -/// e.g. `foo` -> `foo_INPUTS` -fn mk_inputs_ident(base: &Ident) -> Ident { - Ident::new(&format!("{}_INPUTS", base), Span::call_site()) -} - -/// e.g. `foo` -> `foo_INSTANTS` -fn mk_instants_ident(base: &Ident) -> Ident { - Ident::new(&format!("{}_INSTANTS", base), Span::call_site()) -} - -/// e.g. `foo` -> `foo_FQ` -fn mk_fq_ident(base: &Ident) -> Ident { - Ident::new(&format!("{}_FQ", base), Span::call_site()) -} - -/// e.g. `3` -> `RQ3` -fn mk_rq_ident(level: u8) -> Ident { - Ident::new(&format!("RQ{}", level), Span::call_site()) -} - -/// e.g. `3` -> `T3` -fn mk_t_ident(level: u8) -> Ident { - Ident::new(&format!("T{}", level), Span::call_site()) -} - -fn mk_spawn_ident(task: &Ident) -> Ident { - Ident::new(&format!("spawn_{}", task), Span::call_site()) -} - -fn mk_schedule_ident(task: &Ident) -> Ident { - Ident::new(&format!("schedule_{}", task), Span::call_site()) -} - -// Regroups a task inputs -// -// e.g. &[`input: Foo`], &[`mut x: i32`, `ref y: i64`] -fn regroup_inputs( - inputs: &[ArgCaptured], -) -> ( - // args e.g. &[`_0`], &[`_0: i32`, `_1: i64`] - Vec<proc_macro2::TokenStream>, - // tupled e.g. `_0`, `(_0, _1)` - proc_macro2::TokenStream, - // untupled e.g. &[`_0`], &[`_0`, `_1`] - Vec<proc_macro2::TokenStream>, - // ty e.g. `Foo`, `(i32, i64)` - proc_macro2::TokenStream, -) { - 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) - } -} - -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -enum Kind { - Exception(Ident), - Idle, - Init, - Interrupt(Ident), - Task(Ident), -} - -impl Kind { - fn ident(&self) -> Ident { - let span = Span::call_site(); - match self { - Kind::Init => Ident::new("init", span), - Kind::Idle => Ident::new("idle", span), - Kind::Task(name) | Kind::Interrupt(name) | Kind::Exception(name) => name.clone(), - } - } - - fn locals_ident(&self) -> Ident { - Ident::new(&format!("{}Locals", self.ident()), Span::call_site()) - } - - fn resources_ident(&self) -> Ident { - Ident::new(&format!("{}Resources", self.ident()), Span::call_site()) - } - - fn is_idle(&self) -> bool { - *self == Kind::Idle - } - - fn is_init(&self) -> bool { - *self == Kind::Init - } - - fn runs_once(&self) -> bool { - match *self { - Kind::Init | Kind::Idle => true, - _ => false, - } - } -} |