From a452700628e352e6ac01da9e16223a47752ca860 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Sun, 21 Apr 2019 20:02:59 +0200 Subject: [PATCH] implement RFCs 147 and 155, etc. This commit: - Implements RFC 147: "all functions must be safe" - Implements RFC 155: "explicit Context parameter" - Implements the pending breaking change #141: reject assign syntax in `init` (which was used to initialize late resources) - Refactors code generation to make it more readable -- there are no more random identifiers in the output -- and align it with the book description of RTFM internals. - Makes the framework hard depend on `core::mem::MaybeUninit` and thus will require nightly until that API is stabilized. - Fixes a ceiling analysis bug where the priority of the system timer was not considered in the analysis. - Shrinks the size of all the internal queues by turning `AtomicUsize` indices into `AtomicU8`s. - Removes the integration with `owned_singleton`. --- CHANGELOG.md | 29 + Cargo.toml | 11 +- macros/Cargo.toml | 6 +- macros/src/analyze.rs | 28 +- macros/src/check.rs | 21 +- macros/src/codegen.rs | 4036 ++++++++++++++++++++++------------------- macros/src/lib.rs | 6 +- macros/src/syntax.rs | 462 +++-- src/export.rs | 113 +- src/lib.rs | 32 +- src/tq.rs | 78 +- 11 files changed, 2491 insertions(+), 2331 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df4c674d32..fb1102c0ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,35 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +## v0.5.0 - 2019-??-?? (ALPHA pre-release) + +### Changed + +- [breaking-change][] [RFC 155] "explicit `Context` parameter" has been + implemented. + +[RFC 155]: https://github.com/japaric/cortex-m-rtfm/issues/155 + +- [breaking-change][] [RFC 147] "all functions must be safe" has been + implemented. + +[RFC 147]: https://github.com/japaric/cortex-m-rtfm/issues/147 + +- All the queues internally used by the framework now use `AtomicU8` indices + instead of `AtomicUsize`; this reduces the static memory used by the + framework. + +### Removed + +- [breaking-change] the integration with the `owned_singleton` crate has been + removed. You can use `heapless::Pool` instead of `alloc_singleton`. + +- [breaking-change] late resources can no longer be initialized using the assign + syntax. `init::LateResources` is the only method to initialize late resources. + See [PR #140] for more details. + +[PR #140]: https://github.com/japaric/cortex-m-rtfm/pull/140 + ## [v0.4.3] - 2019-04-21 ### Changed diff --git a/Cargo.toml b/Cargo.toml index b102ce783a..236f3091f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ license = "MIT OR Apache-2.0" name = "cortex-m-rtfm" readme = "README.md" repository = "https://github.com/japaric/cortex-m-rtfm" -version = "0.4.3" +version = "0.5.0-alpha.1" [lib] name = "rtfm" @@ -36,12 +36,13 @@ required-features = ["timer-queue"] [dependencies] cortex-m = "0.5.8" cortex-m-rt = "0.6.7" -cortex-m-rtfm-macros = { path = "macros", version = "0.4.3" } -heapless = "0.4.1" -owned-singleton = "0.1.0" +cortex-m-rtfm-macros = { path = "macros", version = "0.5.0-alpha.1" } + +[dependencies.heapless] +features = ["smaller-atomics"] +version = "0.4.1" [dev-dependencies] -alloc-singleton = "0.1.0" cortex-m-semihosting = "0.3.2" lm3s6965 = "0.1.3" panic-halt = "0.2.0" diff --git a/macros/Cargo.toml b/macros/Cargo.toml index d891ba9923..3771869cfa 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0" name = "cortex-m-rtfm-macros" readme = "../README.md" repository = "https://github.com/japaric/cortex-m-rtfm" -version = "0.4.3" +version = "0.5.0-alpha.1" [lib] proc-macro = true @@ -22,10 +22,6 @@ proc-macro2 = "0.4.24" features = ["extra-traits", "full"] version = "0.15.23" -[dependencies.rand] -default-features = false -version = "0.5.5" - [features] timer-queue = [] nightly = [] \ No newline at end of file diff --git a/macros/src/analyze.rs b/macros/src/analyze.rs index cfd8ebc94a..a47be77972 100644 --- a/macros/src/analyze.rs +++ b/macros/src/analyze.rs @@ -190,19 +190,20 @@ pub fn app(app: &App) -> Analysis { } // Ceiling analysis of free queues (consumer end point) -- first pass - // Ceiling analysis of ready queues (producer end point) + // Ceiling analysis of ready queues (producer end point) -- first pass // Also compute more Send-ness requirements - let mut free_queues: HashMap<_, _> = app.tasks.keys().map(|task| (task.clone(), 0)).collect(); - let mut ready_queues: HashMap<_, _> = dispatchers.keys().map(|level| (*level, 0)).collect(); + let mut free_queues = HashMap::new(); + let mut ready_queues = HashMap::new(); for (priority, task) in app.spawn_calls() { if let Some(priority) = priority { - // Users of `spawn` contend for the to-be-spawned task FREE_QUEUE - let c = free_queues.get_mut(task).expect("BUG: free_queue.get_mut"); + // Users of `spawn` contend for the spawnee FREE_QUEUE + let c = free_queues.entry(task.clone()).or_default(); *c = cmp::max(*c, priority); + // Users of `spawn` contend for the spawnee's dispatcher READY_QUEUE let c = ready_queues - .get_mut(&app.tasks[task].args.priority) - .expect("BUG: ready_queues.get_mut"); + .entry(app.tasks[task].args.priority) + .or_default(); *c = cmp::max(*c, priority); // Send is required when sending messages from a task whose priority doesn't match the @@ -215,16 +216,23 @@ pub fn app(app: &App) -> Analysis { } } + // Ceiling analysis of ready queues (producer end point) -- second pass // Ceiling analysis of free queues (consumer end point) -- second pass // Ceiling analysis of the timer queue let mut tq_ceiling = tq_priority; for (priority, task) in app.schedule_calls() { + // the system timer handler contends for the spawnee's dispatcher READY_QUEUE + let c = ready_queues + .entry(app.tasks[task].args.priority) + .or_default(); + *c = cmp::max(*c, tq_priority); + if let Some(priority) = priority { - // Users of `schedule` contend for the to-be-spawned task FREE_QUEUE (consumer end point) - let c = free_queues.get_mut(task).expect("BUG: free_queue.get_mut"); + // Users of `schedule` contend for the spawnee task FREE_QUEUE + let c = free_queues.entry(task.clone()).or_default(); *c = cmp::max(*c, priority); - // Users of `schedule` contend for the timer queu + // Users of `schedule` contend for the timer queue tq_ceiling = cmp::max(tq_ceiling, priority); } else { // spawns from `init` are excluded from the ceiling analysis diff --git a/macros/src/check.rs b/macros/src/check.rs index 4adc2c1756..6ba7d37556 100644 --- a/macros/src/check.rs +++ b/macros/src/check.rs @@ -35,21 +35,12 @@ pub fn app(app: &App) -> parse::Result<()> { } } - // Check that all late resources have been initialized in `#[init]` if `init` has signature - // `fn()` - if !app.init.returns_late_resources { - for res in - app.resources - .iter() - .filter_map(|(name, res)| if res.expr.is_none() { Some(name) } else { None }) - { - if app.init.assigns.iter().all(|assign| assign.left != *res) { - return Err(parse::Error::new( - res.span(), - "late resources MUST be initialized at the end of `init`", - )); - } - } + // Check that `init` returns `LateResources` if there's any declared late resource + if !app.init.returns_late_resources && app.resources.iter().any(|(_, res)| res.expr.is_none()) { + return Err(parse::Error::new( + app.init.span, + "late resources have been specified so `init` must return `init::LateResources`", + )); } // Check that all referenced tasks have been declared diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs index 4468216f53..3fae75b764 100644 --- a/macros/src/codegen.rs +++ b/macros/src/codegen.rs @@ -1,1454 +1,741 @@ -#![deny(warnings)] - use proc_macro::TokenStream; -use std::{ - collections::{BTreeMap, HashMap}, - time::{SystemTime, UNIX_EPOCH}, -}; +use std::collections::{BTreeMap, BTreeSet}; use proc_macro2::Span; use quote::quote; -use rand::{Rng, SeedableRng}; -use syn::{parse_quote, ArgCaptured, Attribute, Ident, IntSuffix, LitInt}; +use syn::{ArgCaptured, Attribute, Ident, IntSuffix, LitInt}; use crate::{ analyze::{Analysis, Ownership}, - syntax::{App, Idents, Static}, + syntax::{App, 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; +pub fn app(name: &Ident, app: &App, analysis: &Analysis) -> TokenStream { + let (const_app_resources, mod_resources) = resources(app, analysis); -struct Context { - // Alias - #[cfg(feature = "timer-queue")] - baseline: Ident, - dispatchers: BTreeMap, - // 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, - // Alias (`enum`) - schedule_enum: Ident, - // Task -> Alias (`fn`) - schedule_fn: Aliases, - tasks: BTreeMap, - // Alias (`struct` / `static mut`) - timer_queue: Ident, - // Generator of Ident names or suffixes - ident_gen: IdentGenerator, -} + let ( + const_app_exceptions, + exception_mods, + exception_locals, + exception_resources, + user_exceptions, + ) = exceptions(app, analysis); -struct Dispatcher { - enum_: Ident, - ready_queue: Ident, -} + let ( + const_app_interrupts, + interrupt_mods, + interrupt_locals, + interrupt_resources, + user_interrupts, + ) = interrupts(app, analysis); -struct Task { - alias: Ident, - free_queue: Ident, - inputs: Ident, - spawn_fn: Ident, + let (const_app_tasks, task_mods, task_locals, task_resources, user_tasks) = + tasks(app, analysis); - #[cfg(feature = "timer-queue")] - scheduleds: Ident, -} + let const_app_dispatchers = dispatchers(&app, analysis); -impl Default for Context { - fn default() -> Self { - let mut ident_gen = IdentGenerator::new(); + let const_app_spawn = spawn(app, analysis); - 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, - } - } -} + let const_app_tq = timer_queue(app, analysis); -struct Resources { - alias: Ident, - decl: proc_macro2::TokenStream, -} + let const_app_schedule = schedule(app); -pub fn app(app: &App, analysis: &Analysis) -> TokenStream { - let mut ctxt = Context::default(); + let assertion_stmts = assertions(app, analysis); - let resources = resources(&mut ctxt, &app, analysis); + let pre_init_stmts = pre_init(&app, analysis); - let tasks = tasks(&mut ctxt, &app, analysis); + let ( + const_app_init, + mod_init, + init_locals, + init_resources, + init_late_resources, + user_init, + call_init, + ) = init(app, analysis); - let (dispatchers_data, dispatchers) = dispatchers(&mut ctxt, &app, analysis); + let post_init_stmts = post_init(&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 (const_app_idle, mod_idle, idle_locals, idle_resources, user_idle, call_idle) = + idle(app, analysis); - 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::>(); - - 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); + let device = &app.args.device; quote!( - #resources + #user_init - #spawn + #user_idle - #timer_queue + #(#user_exceptions)* - #schedule + #(#user_interrupts)* - #dispatchers_data + #(#user_tasks)* - #(#exceptions)* + #mod_resources - #root_interrupts + #init_locals - const APP: () = { - #scoped_interrupts + #init_resources - #(#dispatchers)* + #init_late_resources + + #mod_init + + #idle_locals + + #idle_resources + + #mod_idle + + #(#exception_locals)* + + #(#exception_resources)* + + #(#exception_mods)* + + #(#interrupt_locals)* + + #(#interrupt_resources)* + + #(#interrupt_mods)* + + #(#task_locals)* + + #(#task_resources)* + + #(#task_mods)* + + /// Implementation details + const #name: () = { + // always include the device crate, which contains the vector table + use #device as _; + + #(#const_app_resources)* + + #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_schedule)* + + #[no_mangle] + unsafe fn main() -> ! { + #(#assertion_stmts)* + + #(#pre_init_stmts)* + + #call_init + + #(#post_init_stmts)* + + #call_idle + } }; - - #(#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![]; +/* 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 = 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, + // 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 mut_ = &res.mutability; let ty = &res.ty; - let expr = &res.expr; - if res.singleton { - items.push(quote!( + if let Some(expr) = res.expr.as_ref() { + const_app.push(quote!( #(#attrs)* - pub static #mut_ #name: #ty = #expr; + #(#cfgs)* + 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); + const_app.push(quote!( + #(#attrs)* + #(#cfgs)* + static mut #name: rtfm::export::MaybeUninit<#ty> = + rtfm::export::MaybeUninit::uninit(); + )); } - } - 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); } - ) + if let Some(Ownership::Shared { ceiling }) = analysis.ownerships.get(name) { + let ptr = if res.expr.is_none() { + quote!(#name.as_mut_ptr()) } else { - let left = &assign.left; - let right = &assign.right; - quote!( - #(#attrs)* - #left = #right; - ) - } - }) - .collect::>(); - - 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::>(); - - 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![]; - - 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; - - // compile time assert that the priority is supported by the device - exprs.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];)); - - 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; - - // compile time assert that the priority is supported by the device - exprs.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];)); - - 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, -) -> 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!(), + quote!(&mut #name) }; - 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, + 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 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() { + let mod_resources = if mod_resources.is_empty() { quote!() } else { - quote!( - let ref #priority = unsafe { rtfm::export::Priority::new(#logical_prio) }; + quote!(mod resources { + use rtfm::export::Priority; - #(#items)* - ) - } + #(#mod_resources)* + }) + }; + + (const_app, mod_resources) } -fn idle( - ctxt: &mut Context, +// 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, -) -> (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; +) -> ( + // const_app + Vec, + // exception_mods + Vec, + // exception_locals + Vec, + // exception_resources + Vec, + // user_exceptions + Vec, +) { + 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 prelude = prelude( - ctxt, - Kind::Idle, - &idle.args.resources, - &idle.args.spawn, - &idle.args.schedule, - app, - 0, - analysis, - ); + 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 module = module( - ctxt, - Kind::Idle, - !idle.args.schedule.is_empty(), - !idle.args.spawn.is_empty(), - app, - None, - ); + #let_instant - let unsafety = &idle.unsafety; - let idle = &ctxt.idle; + rtfm::export::run(PRIORITY, || { + crate::#name( + #name::Locals::new(), + #name::Context::new(&rtfm::export::Priority::new(PRIORITY) #instant) + ) + }); + } + )); - ( - 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 { - 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, + 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, ); - let module = module( - ctxt, - kind, - !exception.args.schedule.is_empty(), - !exception.args.spawn.is_empty(), - app, - None, - ); + resources_structs.push(item); - #[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!(), - }; + const_app.push(constructor); + } - 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) { - 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, + 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, - 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 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 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 + let stmts = &exception.stmts; + user_code.push(quote!( #(#attrs)* - #[export_name = #symbol] - unsafe fn #alias() { - #[inline(always)] - #unsafety fn interrupt() { - #(#locals)* + #[allow(non_snake_case)] + fn #name(__locals: #name::Locals, #context: #name::Context) { + #use_u32ext + use rtfm::Mutex as _; - #baseline_let + #(#lets;)* - #prelude - - #start_let - - rtfm::export::run(move || { - #(#stmts)* - }) - } - - interrupt() + #(#stmts)* } )); } - (quote!(#(#root)*), quote!(#(#scoped)*)) + ( + const_app, + mods, + locals_structs, + resources_structs, + user_code, + ) } -fn tasks(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream { - let mut items = vec![]; +// 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, + // interrupt_mods + Vec, + // interrupt_locals + Vec, + // interrupt_resources + Vec, + // user_exceptions + Vec, +) { + let mut const_app = vec![]; + let mut mods = vec![]; + let mut locals_structs = vec![]; + let mut resources_structs = vec![]; + let mut user_code = 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; #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),*]; - )); + 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 { - items.push(quote!( - #[doc = #free_symbol] - static mut #free_alias: rtfm::export::MaybeUninit< - rtfm::export::FreeQueue<#capacity_ty> - > = rtfm::export::MaybeUninit::uninit(); + (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; - #[doc = #inputs_symbol] - static mut #inputs_alias: rtfm::export::MaybeUninit<[#ty; #capacity_lit]> = - rtfm::export::MaybeUninit::uninit(); + #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, + // task_mods + Vec, + // task_locals + Vec, + // task_resources + Vec, + // user_tasks + Vec, +) { + 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!(rtfm::export::MaybeUninit::uninit())) + .collect::>(); + + 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: [rtfm::export::MaybeUninit; #cap_lit] = + [#(#elems,)*]; )); } - items.push(quote!( - #resource - - #scheduleds_static + const_app.push(quote!( + /// Buffer that holds the inputs of a task + static mut #task_inputs: [rtfm::export::MaybeUninit<#ty>; #cap_lit] = + [#(#elems,)*]; )); - 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), + 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>); + let ptr = if cfg!(feature = "nightly") { + const_app.push(quote!( + #[doc = #doc] + static mut #task_fq: #fq_ty = unsafe { + rtfm::export::FreeQueue::u8_sc() + }; + )); - #[cfg(feature = "timer-queue")] - scheduleds: scheduleds_alias, - }, - ); - } + quote!(&mut #task_fq) + } else { + const_app.push(quote!( + #[doc = #doc] + static mut #task_fq: rtfm::export::MaybeUninit<#fq_ty> = + rtfm::export::MaybeUninit::uninit(); + )); - // 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; - - let scheduled_let = match () { - #[cfg(feature = "timer-queue")] - () => { - let baseline = &ctxt.baseline; - quote!(let scheduled = *#baseline;) - } - #[cfg(not(feature = "timer-queue"))] - () => quote!(), + quote!(#task_fq.as_mut_ptr()) }; - let prelude = prelude( - ctxt, - Kind::Task(name.clone()), - &task.args.resources, - &task.args.spawn, - &task.args.schedule, - app, - task.args.priority, - analysis, - ); + if let Some(ceiling) = analysis.free_queues.get(name) { + const_app.push(quote!(struct #task_fq<'a> { + priority: &'a rtfm::export::Priority, + })); - items.push(module( - ctxt, + 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, - None, )); 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 use_u32ext = if cfg!(feature = "timer-queue") { + Some(quote!( + use rtfm::U32Ext as _; + )) + } else { + None }; - let pats = tuple_pat(inputs); - items.push(quote!( - // unsafe trampoline to deter end-users from calling this non-reentrant function + 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)* - #(#cfgs)* - unsafe fn #task_alias(#baseline_arg #(#inputs,)*) { - #[inline(always)] - #unsafety fn task(#baseline_arg #(#inputs,)*) { - #(#locals)* + #[allow(non_snake_case)] + fn #name(__locals: #name::Locals, #context: #name::Context #(,#inputs)*) { + use rtfm::Mutex as _; + #use_u32ext - #prelude + #(#lets;)* - #scheduled_let - - #(#stmts)* - } - - task(#baseline #pats) + #(#stmts)* } )); } - quote!(#(#items)*) + ( + const_app, + mods, + locals_structs, + resources_structs, + user_code, + ) } -fn dispatchers( - ctxt: &mut Context, - app: &App, - analysis: &Analysis, -) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { - let mut data = vec![]; - let mut dispatchers = vec![]; +/// 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 { + let mut items = 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 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>); + let ptr = if cfg!(feature = "nightly") { + items.push(quote!( + #[doc = #doc] + static mut #rq: #rq_ty = unsafe { + rtfm::export::ReadyQueue::u8_sc() + }; + )); + + quote!(&mut #rq) + } else { + items.push(quote!( + #[doc = #doc] + static mut #rq: rtfm::export::MaybeUninit<#rq_ty> = + rtfm::export::MaybeUninit::uninit(); + )); + + quote!(#rq.as_mut_ptr()) + }; + + 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 task_ = &app.tasks[task]; - let cfgs = &task_.cfgs; + let cfgs = &app.tasks[task].cfgs; quote!( #(#cfgs)* @@ -1456,126 +743,100 @@ fn dispatchers( ) }) .collect::>(); - 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, + + let doc = format!( + "Software tasks to be dispatched at priority level {}", + level ); - - 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)] + items.push(quote!( #[allow(non_camel_case_types)] - enum #enum_alias { #(#variants,)* } - - #resource + #[derive(Clone, Copy)] + #[doc = #doc] + enum #t { + #(#variants,)* + } )); 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; + .map(|name| { + let task = &app.tasks[name]; + let cfgs = &task.cfgs; + let (_, tupled, pats, _) = regroup_inputs(&task.inputs); - let task__ = &app.tasks[task]; - let pats = tuple_pat(&task__.inputs); - let cfgs = &task__.cfgs; + let inputs = mk_inputs_ident(name); + let fq = mk_fq_ident(name); - 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 input = quote!(#inputs.get_unchecked(usize::from(index)).read()); + let fq = if cfg!(feature = "nightly") { + quote!(#fq) + } else { + quote!((*#fq.as_mut_ptr())) }; - let (free_, input) = if cfg!(feature = "nightly") { + let (let_instant, _instant) = if cfg!(feature = "timer-queue") { + let instants = mk_instants_ident(name); + let instant = quote!(#instants.get_unchecked(usize::from(index)).read()); + ( - quote!(#free), - quote!(#inputs.get_unchecked(usize::from(index)).as_ptr()), + Some(quote!(let instant = #instant;)), + Some(quote!(, instant)), ) } else { - ( - quote!(#free.get_mut()), - quote!(#inputs.get_ref().get_unchecked(usize::from(index))), + (None, None) + }; + + let call = { + let pats = pats.clone(); + + quote!( + #name( + #name::Locals::new(), + #name::Context::new(priority #_instant) + #(,#pats)* + ) ) }; quote!( #(#cfgs)* - #enum_alias::#task => { - #baseline_let - let input = ptr::read(#input); - #free_.split().0.enqueue_unchecked(index); - let (#pats) = input; + #t::#name => { + let #tupled = #input; + #let_instant + #fq.split().0.enqueue_unchecked(index); + let priority = &rtfm::export::Priority::new(PRIORITY); #call } ) }) .collect::>(); + let doc = format!( + "interrupt handler used to dispatch tasks at priority {}", + level + ); 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) + let rq = if cfg!(feature = "nightly") { + quote!((&mut #rq)) } else { - quote!(#ready_alias.get_mut()) + quote!((*#rq.as_mut_ptr())) }; - dispatchers.push(quote!( + items.push(quote!( + #[doc = #doc] #(#attrs)* - #[export_name = #symbol] - unsafe fn #alias() { - use core::ptr; + #[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; + let _ = #device::Interrupt::#interrupt; - rtfm::export::run(|| { - while let Some((task, index)) = #ready_alias_.split().1.dequeue() { + rtfm::export::run(PRIORITY, || { + while let Some((task, index)) = #rq.split().1.dequeue() { match task { #(#arms)* } @@ -1583,269 +844,127 @@ fn dispatchers( }); } )); - - ctxt.dispatchers.insert( - *level, - Dispatcher { - ready_queue: ready_alias, - enum_: enum_alias, - }, - ); } - (quote!(#(#data)*), quote!(#(#dispatchers)*)) + items } -fn spawn(ctxt: &Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream { +/// Generates all the `Spawn.$task` related code +fn spawn(app: &App, analysis: &Analysis) -> Vec { 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)) - } - } - )) - } - - // Generate `spawn` structs; these call the `spawn` functions generated above - for (name, spawn) in app.spawn_callers() { - if spawn.is_empty() { + let mut seen = BTreeSet::new(); + for (spawner, spawnees) in app.spawn_callers() { + if spawnees.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,) + 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 + } + )); } - #[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) } - } - )); + + 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<'a> #name::Spawn<'a> { + impl<#lt> #spawner::Spawn<#lt> { #(#methods)* } )); } - quote!(#(#items)*) + items } -#[cfg(feature = "timer-queue")] -fn schedule(ctxt: &Context, app: &App) -> proc_macro2::TokenStream { +/// 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 { 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)) - } - } - )) - } - - // 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); - - 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!(); + return items; } - let mut items = vec![]; - let variants = tasks .iter() .map(|task| { @@ -1856,273 +975,1392 @@ fn timer_queue(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro ) }) .collect::>(); - let enum_ = &ctxt.schedule_enum; + items.push(quote!( - #[allow(dead_code)] + /// `schedule`-dable tasks #[allow(non_camel_case_types)] #[derive(Clone, Copy)] - enum #enum_ { #(#variants,)* } + enum T { + #(#variants,)* + } )); 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::MaybeUninit::uninit(); - )); - } else { - items.push(quote!( - #[doc = #symbol] - static mut #tq: - rtfm::export::MaybeUninit> = - 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, + let ty = quote!(rtfm::export::TimerQueue); + items.push(quote!( + /// The timer queue + static mut TQ: rtfm::export::MaybeUninit<#ty> = rtfm::export::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 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; + .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)* - #enum_::#task => { - (#ready { #priority }).lock(|rq| { - rq.split().0.enqueue_unchecked((#tenum::#task, index)) + 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); + rtfm::pend(#device::Interrupt::#dispatcher) } ) }) .collect::>(); - let logical_prio = analysis.timer_queue.priority; - let alias = ctxt.ident_gen.mk_ident(None, false); + let priority = analysis.timer_queue.priority; items.push(quote!( - #[export_name = "SysTick"] - #[doc(hidden)] - unsafe fn #alias() { - use rtfm::Mutex; + /// The system timer + #[no_mangle] + unsafe fn SysTick() { + use rtfm::Mutex as _; - let ref #priority = rtfm::export::Priority::new(#logical_prio); + /// System timer priority + const PRIORITY: u8 = #priority; - rtfm::export::run(|| { - rtfm::export::sys_tick(#tq { #priority }, |task, index| { + 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)* } - }); - }) + } + }); } )); - quote!(#(#items)*) + items } -fn pre_init(ctxt: &Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream { - let mut exprs = vec![]; +/// Generates all the `Schedule.$task` related code +fn schedule(app: &App) -> Vec { + 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 { + 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 { + let mut stmts = vec![]; + + stmts.push(quote!(rtfm::export::interrupt::disable();)); + + // these won't be required once we have better `const fn` on stable (or const generics) 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());)) + // initialize `MaybeUninit` `ReadyQueue`s + for level in analysis.dispatchers.keys() { + let rq = mk_rq_ident(*level); + stmts.push(quote!(#rq.write(rtfm::export::ReadyQueue::u8_sc());)) } - #[cfg(feature = "timer-queue")] - for task in ctxt.tasks.values() { - let scheduleds = &task.scheduleds; - exprs.push(quote!(#scheduleds.write(core::mem::uninitialized());)) - } + // initialize `MaybeUninit` `FreeQueue`s + for name in app.tasks.keys() { + let fq = mk_fq_ident(name); - // 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());)) - } + stmts.push(quote!( + let fq = #fq.write(rtfm::export::FreeQueue::u8_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());)) + // populate the `FreeQueue`s + let cap = analysis.capacities[name]; + stmts.push(quote!( + for i in 0..#cap { + fq.enqueue_unchecked(i); + } + )); + } + } else { + // 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() { - 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); - } - )) + stmts.push(quote!(TQ.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; - exprs.push(quote!(p.NVIC.enable(#device::Interrupt::#name);)); - // compile time assert that the priority is supported by the device - exprs.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];)); + stmts.push(quote!(core.NVIC.enable(#device::Interrupt::#name);)); - exprs.push(quote!(p.NVIC.set_priority( - #device::Interrupt::#name, - ((1 << #nvic_prio_bits) - #priority) << (8 - #nvic_prio_bits), - );)); + // 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; - exprs.push(quote!(p.NVIC.enable(#device::Interrupt::#name);)); - // compile time assert that the priority is supported by the device - exprs.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];)); + stmts.push(quote!(core.NVIC.enable(#device::Interrupt::#name);)); - exprs.push(quote!(p.NVIC.set_priority( - #device::Interrupt::#name, - ((1 << #nvic_prio_bits) - #priority) << (8 - #nvic_prio_bits), - );)); + // 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") { - exprs.push(quote!(p.DWT.ctrl.modify(|r| r & !1);)); - exprs.push(quote!(p.DWT.cyccnt.write(0);)); + stmts.push(quote!(core.DWT.ctrl.modify(|r| r & !1);)); + stmts.push(quote!(core.DWT.cyccnt.write(0);)); } - quote!( - let mut p = rtfm::export::Peripherals::steal(); - #(#exprs)* + 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, + // mod_init + proc_macro2::TokenStream, + // init_locals + proc_macro2::TokenStream, + // init_resources + Option, + // init_late_resources + Option, + // 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::>(); + + 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, ) } -fn assertions(app: &App, analysis: &Analysis) -> proc_macro2::TokenStream { - let mut items = vec![]; +/// Generates code that we must run after `init` returns. See comments inside +fn post_init(app: &App, analysis: &Analysis) -> Vec { + let mut stmts = vec![]; - for ty in &analysis.assert_sync { - items.push(quote!(rtfm::export::assert_sync::<#ty>())); + 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.write(late.#name);)); } - for task in &analysis.tasks_assert_send { - let ty = tuple_ty(&app.tasks[task].inputs); - items.push(quote!(rtfm::export::assert_send::<#ty>())); + // 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), + );)); } - // all late resources need to be `Send` - for ty in &analysis.resources_assert_send { - items.push(quote!(rtfm::export::assert_send::<#ty>())); + // set the system timer priority + if !analysis.timer_queue.tasks.is_empty() { + let priority = analysis.timer_queue.priority; + + // compile time assert that this priority is supported by the device + stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];)); + + stmts.push(quote!(core.SCB.set_priority( + rtfm::export::SystemHandler::SysTick, + rtfm::export::logical2hw(#priority, #nvic_prio_bits), + );)); } - quote!(#(#items;)*) + 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);)); + } + + // 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 } -fn mk_resource( - ctxt: &Context, +// 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, + // mod_idle + Option, + // idle_locals + Option, + // idle_resources + Option, + // user_idle + Option, + // 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 mod_idle = module( + Kind::Idle, + (!idle.args.resources.is_empty(), needs_lt), + !idle.args.schedule.is_empty(), + !idle.args.spawn.is_empty(), + false, + app, + ); + + ( + 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], - struct_: &Ident, + resources_prefix: bool, + name: &Ident, ty: proc_macro2::TokenStream, ceiling: u8, ptr: proc_macro2::TokenStream, - app: &App, - module: Option<&mut Vec>, ) -> proc_macro2::TokenStream { - let priority = &ctxt.priority; - let device = &app.args.device; - - let mut items = vec![]; - - 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, - } - )); - - quote!(resources::#struct_) + let path = if resources_prefix { + quote!(resources::#name) } else { - items.push(quote!( - #(#cfgs)* - struct #struct_<'a> { - #priority: &'a rtfm::export::Priority, - } - )); - - quote!(#struct_) + quote!(#name) }; - items.push(quote!( + let priority = if resources_prefix { + quote!(self.priority()) + } else { + quote!(self.priority) + }; + + let device = &app.args.device; + quote!( #(#cfgs)* impl<'a> rtfm::Mutex for #path<'a> { type T = #ty; - #[inline] - fn lock(&mut self, f: F) -> R - where - F: FnOnce(&mut Self::T) -> R, - { + #[inline(always)] + fn lock(&mut self, f: impl FnOnce(&mut #ty) -> R) -> R { + /// Priority ceiling + const CEILING: u8 = #ceiling; + unsafe { - rtfm::export::claim( + rtfm::export::lock( #ptr, - &self.#priority, - #ceiling, + #priority, + CEILING, #device::NVIC_PRIO_BITS, f, ) } } } - )); - - quote!(#(#items)*) + ) } +/// 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, +) -> ( + // locals + proc_macro2::TokenStream, + // lets + Vec, +) { + 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)); + } + + let locals = quote!( + #[allow(non_snake_case)] + #[doc(hidden)] + pub struct #ident<#lt> { + #(#fields),* + } + + impl<#lt> #ident<#lt> { + #[inline(always)] + unsafe fn new() -> Self { + #(#items;)* + + #ident { + #(#values),* + } + } + } + ); + + (locals, lets) +} + +/// 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![]; + + let name = kind.ident(); + + 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 + )); + + 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)); + + needs_instant = true; + } + } + + Kind::Task(_) => { + if cfg!(feature = "timer-queue") { + fields.push(quote!( + /// The time at which this task was scheduled to run + pub scheduled: rtfm::Instant + )); + + values.push(quote!(scheduled: instant)); + + needs_instant = true; + } + } + } + + let ident = kind.locals_ident(); + items.push(quote!( + #[doc(inline)] + pub use super::#ident as Locals; + )); + + if resources.0 { + let ident = kind.resources_ident(); + let lt = if resources.1 { + lt = Some(quote!('a)); + Some(quote!('a)) + } else { + None + }; + + items.push(quote!( + #[doc(inline)] + pub use super::#ident as Resources; + )); + + fields.push(quote!( + /// Resources this task has access to + pub resources: Resources<#lt> + )); + + let priority = if kind.is_init() { + None + } else { + Some(quote!(priority)) + }; + 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)).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 + if cfg!(feature = "nightly") { + ( + quote!(#fq.dequeue()), + quote!(#rq.enqueue_unchecked((#t::#name, index));), + ) + } else { + ( + quote!((*#fq.as_mut_ptr()).dequeue()), + quote!((*#rq.as_mut_ptr()).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)).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 = if cfg!(feature = "nightly") { + quote!(#fq.dequeue()) + } else { + quote!((*#fq.as_mut_ptr()).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)).write(instant); + + #inputs.get_unchecked_mut(usize::from(index)).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 @@ -2137,108 +2375,85 @@ fn mk_typenum_capacity(capacity: u8, power_of_two: bool) -> proc_macro2::TokenSt quote!(rtfm::export::consts::#ident) } -struct IdentGenerator { - call_count: u32, - rng: rand::rngs::SmallRng, +/// e.g. `foo` -> `foo_INPUTS` +fn mk_inputs_ident(base: &Ident) -> Ident { + Ident::new(&format!("{}_INPUTS", base), Span::call_site()) } -impl IdentGenerator { - fn new() -> IdentGenerator { - let elapsed = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); - - let secs = elapsed.as_secs(); - let nanos = elapsed.subsec_nanos(); - - let mut seed: [u8; 16] = [0; 16]; - - for (i, v) in seed.iter_mut().take(8).enumerate() { - *v = ((secs >> (i * 8)) & 0xFF) as u8 - } - - for (i, v) in seed.iter_mut().skip(8).take(4).enumerate() { - *v = ((nanos >> (i * 8)) & 0xFF) as u8 - } - - let rng = rand::rngs::SmallRng::from_seed(seed); - - IdentGenerator { call_count: 0, rng } - } - - 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() - }; - - let mut s = format!("{}{}", s, self.call_count); - self.call_count += 1; - - if random { - s.push('_'); - - for i in 0..4 { - if i == 0 || self.rng.gen() { - s.push(('a' as u8 + self.rng.gen::() % 25) as char) - } else { - s.push(('0' as u8 + self.rng.gen::() % 10) as char) - } - } - } - - Ident::new(&s, Span::call_site()) - } +/// e.g. `foo` -> `foo_INSTANTS` +fn mk_instants_ident(base: &Ident) -> Ident { + Ident::new(&format!("{}_INSTANTS", base), Span::call_site()) } -// `once = true` means that these locals will be called from a function that will run *once* -fn mk_locals(locals: &BTreeMap, 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; - - quote!( - #[allow(non_snake_case)] - #(#cfgs)* - let #ident: &#lt mut #ty = { - #(#attrs)* - #(#cfgs)* - static mut #ident: #ty = #expr; - - unsafe { &mut #ident } - }; - ) - }) - .collect::>(); - - quote!(#(#locals)*) +/// e.g. `foo` -> `foo_FQ` +fn mk_fq_ident(base: &Ident) -> Ident { + Ident::new(&format!("{}_FQ", base), Span::call_site()) } -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::>(); - - quote!(#(#pats,)*) - } +/// e.g. `3` -> `RQ3` +fn mk_rq_ident(level: u8) -> Ident { + Ident::new(&format!("RQ{}", level), Span::call_site()) } -fn tuple_ty(inputs: &[ArgCaptured]) -> proc_macro2::TokenStream { +/// 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, + // tupled e.g. `_0`, `(_0, _1)` + proc_macro2::TokenStream, + // untupled e.g. &[`_0`], &[`_0`, `_1`] + Vec, + // ty e.g. `Foo`, `(i32, i64)` + proc_macro2::TokenStream, +) { if inputs.len() == 1 { let ty = &inputs[0].ty; - quote!(#ty) - } else { - let tys = inputs.iter().map(|i| &i.ty).collect::>(); - quote!((#(#tys,)*)) + ( + 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) } } @@ -2253,13 +2468,22 @@ enum Kind { impl Kind { fn ident(&self) -> Ident { + let span = Span::call_site(); match self { - Kind::Init => Ident::new("init", Span::call_site()), - Kind::Idle => Ident::new("idle", Span::call_site()), + 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 } diff --git a/macros/src/lib.rs b/macros/src/lib.rs index c8d9fee190..441d6b5ee9 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -288,9 +288,9 @@ mod syntax; pub fn app(args: TokenStream, input: TokenStream) -> TokenStream { // Parse let args = parse_macro_input!(args as syntax::AppArgs); - let items = parse_macro_input!(input as syntax::Input).items; + let input = parse_macro_input!(input as syntax::Input); - let app = match syntax::App::parse(items, args) { + let app = match syntax::App::parse(input.items, args) { Err(e) => return e.to_compile_error().into(), Ok(app) => app, }; @@ -304,5 +304,5 @@ pub fn app(args: TokenStream, input: TokenStream) -> TokenStream { let analysis = analyze::app(&app); // Code generation - codegen::app(&app, &analysis).into() + codegen::app(&input.ident, &app, &analysis).into() } diff --git a/macros/src/syntax.rs b/macros/src/syntax.rs index 228d9588e0..c6814d5f25 100644 --- a/macros/src/syntax.rs +++ b/macros/src/syntax.rs @@ -11,8 +11,8 @@ use syn::{ spanned::Spanned, token::Brace, ArgCaptured, AttrStyle, Attribute, Expr, FnArg, ForeignItem, Ident, IntSuffix, Item, ItemFn, - ItemForeignMod, ItemStatic, LitInt, Path, PathArguments, PathSegment, ReturnType, Stmt, Token, - Type, TypeTuple, Visibility, + ItemForeignMod, ItemStatic, LitInt, Pat, Path, PathArguments, ReturnType, Stmt, Token, Type, + TypeTuple, Visibility, }; pub struct AppArgs { @@ -70,7 +70,7 @@ impl Parse for AppArgs { pub struct Input { _const_token: Token![const], - _ident: Ident, + pub ident: Ident, _colon_token: Token![:], _ty: TypeTuple, _eq_token: Token![=], @@ -94,7 +94,7 @@ impl Parse for Input { let content; Ok(Input { _const_token: input.parse()?, - _ident: input.parse()?, + ident: input.parse()?, _colon_token: input.parse()?, _ty: input.parse()?, _eq_token: input.parse()?, @@ -435,7 +435,7 @@ pub type FreeInterrupts = BTreeMap; pub struct Idle { pub args: IdleArgs, pub attrs: Vec, - pub unsafety: Option, + pub context: Pat, pub statics: BTreeMap, pub stmts: Vec, } @@ -444,34 +444,29 @@ pub type IdleArgs = InitArgs; impl Idle { fn check(args: IdleArgs, item: ItemFn) -> parse::Result { - let valid_signature = item.vis == Visibility::Inherited - && item.constness.is_none() - && item.asyncness.is_none() - && item.abi.is_none() - && item.decl.generics.params.is_empty() - && item.decl.generics.where_clause.is_none() - && item.decl.inputs.is_empty() - && item.decl.variadic.is_none() - && is_bottom(&item.decl.output); + let valid_signature = + check_signature(&item) && item.decl.inputs.len() == 1 && is_bottom(&item.decl.output); let span = item.span(); - if !valid_signature { - return Err(parse::Error::new( - span, - "`idle` must have type signature `[unsafe] fn() -> !`", - )); + if valid_signature { + if let Some((context, _)) = check_inputs(item.decl.inputs, "idle") { + let (statics, stmts) = extract_statics(item.block.stmts); + + return Ok(Idle { + args, + attrs: item.attrs, + context, + statics: Static::parse(statics)?, + stmts, + }); + } } - let (statics, stmts) = extract_statics(item.block.stmts); - - Ok(Idle { - args, - attrs: item.attrs, - unsafety: item.unsafety, - statics: Static::parse(statics)?, - stmts, - }) + Err(parse::Error::new( + span, + "`idle` must have type signature `fn(idle::Context) -> !`", + )) } } @@ -596,34 +591,21 @@ impl Parse for InitArgs { } } -// TODO remove in v0.5.x -pub struct Assign { - pub attrs: Vec, - pub left: Ident, - pub right: Box, -} - pub struct Init { pub args: InitArgs, pub attrs: Vec, - pub unsafety: Option, pub statics: BTreeMap, + pub context: Pat, pub stmts: Vec, - // TODO remove in v0.5.x - pub assigns: Vec, pub returns_late_resources: bool, + pub span: Span, } impl Init { fn check(args: InitArgs, item: ItemFn) -> parse::Result { - let mut valid_signature = item.vis == Visibility::Inherited - && item.constness.is_none() - && item.asyncness.is_none() - && item.abi.is_none() - && item.decl.generics.params.is_empty() - && item.decl.generics.where_clause.is_none() - && item.decl.inputs.is_empty() - && item.decl.variadic.is_none(); + let mut valid_signature = check_signature(&item) && item.decl.inputs.len() == 1; + + const DONT_CARE: bool = false; let returns_late_resources = match &item.decl.output { ReturnType::Default => false, @@ -636,36 +618,25 @@ impl Init { } else { valid_signature = false; - false // don't care + DONT_CARE } } - Type::Path(p) => { - let mut segments = p.path.segments.iter(); - if p.qself.is_none() - && p.path.leading_colon.is_none() - && p.path.segments.len() == 2 - && segments.next().map(|s| { - s.arguments == PathArguments::None && s.ident.to_string() == "init" - }) == Some(true) - && segments.next().map(|s| { - s.arguments == PathArguments::None - && s.ident.to_string() == "LateResources" - }) == Some(true) - { + Type::Path(_) => { + if is_path(ty, &["init", "LateResources"]) { // -> init::LateResources true } else { valid_signature = false; - false // don't care + DONT_CARE } } _ => { valid_signature = false; - false // don't care + DONT_CARE } } } @@ -673,29 +644,26 @@ impl Init { let span = item.span(); - if !valid_signature { - return Err(parse::Error::new( - span, - "`init` must have type signature `[unsafe] fn() [-> init::LateResources]`", - )); + if valid_signature { + if let Some((context, _)) = check_inputs(item.decl.inputs, "init") { + let (statics, stmts) = extract_statics(item.block.stmts); + + return Ok(Init { + args, + attrs: item.attrs, + statics: Static::parse(statics)?, + context, + stmts, + returns_late_resources, + span, + }); + } } - let (statics, stmts) = extract_statics(item.block.stmts); - let (stmts, assigns) = if returns_late_resources { - (stmts, vec![]) - } else { - extract_assignments(stmts) - }; - - Ok(Init { - args, - attrs: item.attrs, - unsafety: item.unsafety, - statics: Static::parse(statics)?, - stmts, - assigns, - returns_late_resources, - }) + Err(parse::Error::new( + span, + "`init` must have type signature `fn(init::Context) [-> init::LateResources]`", + )) } } @@ -725,8 +693,8 @@ impl Default for Args { pub struct Exception { pub args: ExceptionArgs, pub attrs: Vec, - pub unsafety: Option, pub statics: BTreeMap, + pub context: Pat, pub stmts: Vec, } @@ -770,61 +738,67 @@ impl Parse for ExceptionArgs { impl Exception { fn check(args: ExceptionArgs, item: ItemFn) -> parse::Result { - let valid_signature = item.vis == Visibility::Inherited - && item.constness.is_none() - && item.asyncness.is_none() - && item.abi.is_none() - && item.decl.generics.params.is_empty() - && item.decl.generics.where_clause.is_none() - && item.decl.inputs.is_empty() - && item.decl.variadic.is_none() - && is_unit(&item.decl.output); + let valid_signature = + check_signature(&item) && item.decl.inputs.len() == 1 && is_unit(&item.decl.output); - if !valid_signature { - return Err(parse::Error::new( - item.span(), - "`exception` handlers must have type signature `[unsafe] fn()`", - )); - } + let span = item.span(); - let span = item.ident.span(); - match &*args.binds.as_ref().unwrap_or(&item.ident).to_string() { - "MemoryManagement" | "BusFault" | "UsageFault" | "SecureFault" | "SVCall" - | "DebugMonitor" | "PendSV" => {} // OK - "SysTick" => { - if cfg!(feature = "timer-queue") { - return Err(parse::Error::new( + let name = item.ident.to_string(); + if valid_signature { + if let Some((context, _)) = check_inputs(item.decl.inputs, &name) { + let span = item.ident.span(); + match &*args + .binds + .as_ref() + .map(|ident| ident.to_string()) + .unwrap_or(name) + { + "MemoryManagement" | "BusFault" | "UsageFault" | "SecureFault" | "SVCall" + | "DebugMonitor" | "PendSV" => {} // OK + "SysTick" => { + if cfg!(feature = "timer-queue") { + return Err(parse::Error::new( + span, + "the `SysTick` exception can't be used because it's used by \ + the runtime when the `timer-queue` feature is enabled", + )); + } + } + _ => { + return Err(parse::Error::new( span, - "the `SysTick` exception can't be used because it's used by \ - the runtime when the `timer-queue` feature is enabled", + "only exceptions with configurable priority can be used as hardware tasks", )); + } } - } - _ => { - return Err(parse::Error::new( - span, - "only exceptions with configurable priority can be used as hardware tasks", - )); + + let (statics, stmts) = extract_statics(item.block.stmts); + + return Ok(Exception { + args, + attrs: item.attrs, + statics: Static::parse(statics)?, + context, + stmts, + }); } } - let (statics, stmts) = extract_statics(item.block.stmts); - - Ok(Exception { - args, - attrs: item.attrs, - unsafety: item.unsafety, - statics: Static::parse(statics)?, - stmts, - }) + Err(parse::Error::new( + span, + &format!( + "this `exception` handler must have type signature `fn({}::Context)`", + name + ), + )) } } pub struct Interrupt { pub args: InterruptArgs, pub attrs: Vec, - pub unsafety: Option, pub statics: BTreeMap, + pub context: Pat, pub stmts: Vec, } @@ -832,49 +806,47 @@ pub type InterruptArgs = ExceptionArgs; impl Interrupt { fn check(args: InterruptArgs, item: ItemFn) -> parse::Result { - let valid_signature = item.vis == Visibility::Inherited - && item.constness.is_none() - && item.asyncness.is_none() - && item.abi.is_none() - && item.decl.generics.params.is_empty() - && item.decl.generics.where_clause.is_none() - && item.decl.inputs.is_empty() - && item.decl.variadic.is_none() - && is_unit(&item.decl.output); + let valid_signature = + check_signature(&item) && item.decl.inputs.len() == 1 && is_unit(&item.decl.output); let span = item.span(); - if !valid_signature { - return Err(parse::Error::new( - span, - "`interrupt` handlers must have type signature `[unsafe] fn()`", - )); - } + let name = item.ident.to_string(); + if valid_signature { + if let Some((context, _)) = check_inputs(item.decl.inputs, &name) { + match &*name { + "init" | "idle" | "resources" => { + return Err(parse::Error::new( + span, + "`interrupt` handlers can NOT be named `idle`, `init` or `resources`", + )); + } + _ => {} + } - match &*item.ident.to_string() { - "init" | "idle" | "resources" => { - return Err(parse::Error::new( - span, - "`interrupt` handlers can NOT be named `idle`, `init` or `resources`", - )); + let (statics, stmts) = extract_statics(item.block.stmts); + + return Ok(Interrupt { + args, + attrs: item.attrs, + statics: Static::parse(statics)?, + context, + stmts, + }); } - _ => {} } - let (statics, stmts) = extract_statics(item.block.stmts); - - Ok(Interrupt { - args, - attrs: item.attrs, - unsafety: item.unsafety, - statics: Static::parse(statics)?, - stmts, - }) + Err(parse::Error::new( + span, + format!( + "this `interrupt` handler must have type signature `fn({}::Context)`", + name + ), + )) } } pub struct Resource { - pub singleton: bool, pub cfgs: Vec, pub attrs: Vec, pub mutability: Option, @@ -883,7 +855,7 @@ pub struct Resource { } impl Resource { - fn check(mut item: ItemStatic) -> parse::Result { + fn check(item: ItemStatic) -> parse::Result { if item.vis != Visibility::Inherited { return Err(parse::Error::new( item.span(), @@ -896,19 +868,9 @@ impl Resource { _ => false, }; - let pos = item.attrs.iter().position(|attr| eq(attr, "Singleton")); - - if let Some(pos) = pos { - item.attrs[pos].path.segments.insert( - 0, - PathSegment::from(Ident::new("owned_singleton", Span::call_site())), - ); - } - let (cfgs, attrs) = extract_cfgs(item.attrs); Ok(Resource { - singleton: pos.is_some(), cfgs, attrs, mutability: item.mutability, @@ -1177,66 +1139,61 @@ pub struct Task { pub args: TaskArgs, pub cfgs: Vec, pub attrs: Vec, - pub unsafety: Option, pub inputs: Vec, + pub context: Pat, pub statics: BTreeMap, pub stmts: Vec, } impl Task { fn check(args: TaskArgs, item: ItemFn) -> parse::Result { - let valid_signature = item.vis == Visibility::Inherited - && item.constness.is_none() - && item.asyncness.is_none() - && item.abi.is_none() - && item.decl.generics.params.is_empty() - && item.decl.generics.where_clause.is_none() - && item.decl.variadic.is_none() - && is_unit(&item.decl.output); + let valid_signature = + check_signature(&item) && !item.decl.inputs.is_empty() && is_unit(&item.decl.output); let span = item.span(); - if !valid_signature { - return Err(parse::Error::new( - span, - "`task` handlers must have type signature `[unsafe] fn(..)`", - )); - } + let name = item.ident.to_string(); + if valid_signature { + if let Some((context, rest)) = check_inputs(item.decl.inputs, &name) { + let (statics, stmts) = extract_statics(item.block.stmts); - let (statics, stmts) = extract_statics(item.block.stmts); + let inputs = rest.map_err(|arg| { + parse::Error::new( + arg.span(), + "inputs must be named arguments (e.f. `foo: u32`) and not include `self`", + ) + })?; - let mut inputs = vec![]; - for input in item.decl.inputs { - if let FnArg::Captured(capture) = input { - inputs.push(capture); - } else { - return Err(parse::Error::new( - span, - "inputs must be named arguments (e.f. `foo: u32`) and not include `self`", - )); + match &*name { + "init" | "idle" | "resources" => { + return Err(parse::Error::new( + span, + "`task` handlers can NOT be named `idle`, `init` or `resources`", + )); + } + _ => {} + } + + let (cfgs, attrs) = extract_cfgs(item.attrs); + return Ok(Task { + args, + cfgs, + attrs, + inputs, + context, + statics: Static::parse(statics)?, + stmts, + }); } } - match &*item.ident.to_string() { - "init" | "idle" | "resources" => { - return Err(parse::Error::new( - span, - "`task` handlers can NOT be named `idle`, `init` or `resources`", - )); - } - _ => {} - } - - let (cfgs, attrs) = extract_cfgs(item.attrs); - Ok(Task { - args, - cfgs, - attrs, - unsafety: item.unsafety, - inputs, - statics: Static::parse(statics)?, - stmts, - }) + Err(parse::Error::new( + span, + &format!( + "this `task` handler must have type signature `fn({}::Context, ..)`", + name + ), + )) } } @@ -1335,38 +1292,69 @@ fn extract_statics(stmts: Vec) -> (Statics, Vec) { (statics, stmts) } -// TODO remove in v0.5.x -fn extract_assignments(stmts: Vec) -> (Vec, Vec) { - let mut istmts = stmts.into_iter().rev(); +// checks that the list of arguments has the form `#pat: #name::Context, (..)` +// +// if the check succeeds it returns `#pat` plus the remaining arguments +fn check_inputs( + inputs: Punctuated, + name: &str, +) -> Option<(Pat, Result, FnArg>)> { + let mut inputs = inputs.into_iter(); - let mut assigns = vec![]; - let mut stmts = vec![]; - while let Some(stmt) = istmts.next() { - match stmt { - Stmt::Semi(Expr::Assign(assign), semi) => { - if let Expr::Path(ref expr) = *assign.left { - if expr.path.segments.len() == 1 { - assigns.push(Assign { - attrs: assign.attrs, - left: expr.path.segments[0].ident.clone(), - right: assign.right, - }); - continue; - } - } + match inputs.next() { + Some(FnArg::Captured(first)) => { + if is_path(&first.ty, &[name, "Context"]) { + let rest = inputs + .map(|arg| match arg { + FnArg::Captured(arg) => Ok(arg), + _ => Err(arg), + }) + .collect::, _>>(); - stmts.push(Stmt::Semi(Expr::Assign(assign), semi)); - } - _ => { - stmts.push(stmt); - break; + Some((first.pat, rest)) + } else { + None } } + + _ => None, } +} - stmts.extend(istmts); +/// checks that a function signature +/// +/// - has no bounds (like where clauses) +/// - is not `async` +/// - is not `const` +/// - is not `unsafe` +/// - is not generic (has no type parametrs) +/// - is not variadic +/// - uses the Rust ABI (and not e.g. "C") +fn check_signature(item: &ItemFn) -> bool { + item.vis == Visibility::Inherited + && item.constness.is_none() + && item.asyncness.is_none() + && item.abi.is_none() + && item.unsafety.is_none() + && item.decl.generics.params.is_empty() + && item.decl.generics.where_clause.is_none() + && item.decl.variadic.is_none() +} - (stmts.into_iter().rev().collect(), assigns) +fn is_path(ty: &Type, segments: &[&str]) -> bool { + match ty { + Type::Path(tpath) if tpath.qself.is_none() => { + tpath.path.segments.len() == segments.len() + && tpath + .path + .segments + .iter() + .zip(segments) + .all(|(lhs, rhs)| lhs.ident == **rhs) + } + + _ => false, + } } fn is_bottom(ty: &ReturnType) -> bool { diff --git a/src/export.rs b/src/export.rs index cf7293b620..93a92fcf2a 100644 --- a/src/export.rs +++ b/src/export.rs @@ -1,7 +1,5 @@ //! IMPLEMENTATION DETAILS. DO NOT USE ANYTHING IN THIS MODULE -#[cfg(not(feature = "nightly"))] -use core::ptr; use core::{cell::Cell, u8}; #[cfg(armv7m)] @@ -14,25 +12,31 @@ pub use heapless::consts; use heapless::spsc::{Queue, SingleCore}; #[cfg(feature = "timer-queue")] -pub use crate::tq::{isr as sys_tick, NotReady, TimerQueue}; +pub use crate::tq::{NotReady, TimerQueue}; -pub type FreeQueue = Queue; -pub type ReadyQueue = Queue<(T, u8), N, usize, SingleCore>; +pub type FreeQueue = Queue; +pub type ReadyQueue = Queue<(T, u8), N, u8, SingleCore>; #[cfg(armv7m)] #[inline(always)] -pub fn run(f: F) +pub fn run(priority: u8, f: F) where F: FnOnce(), { - let initial = basepri::read(); - f(); - unsafe { basepri::write(initial) } + if priority == 1 { + // if the priority of this interrupt is `1` then BASEPRI can only be `0` + f(); + unsafe { basepri::write(0) } + } else { + let initial = basepri::read(); + f(); + unsafe { basepri::write(initial) } + } } #[cfg(not(armv7m))] #[inline(always)] -pub fn run(f: F) +pub fn run(_priority: u8, f: F) where F: FnOnce(), { @@ -52,7 +56,7 @@ impl Priority { } } - // these two methods are used by claim (see below) but can't be used from the RTFM application + // these two methods are used by `lock` (see below) but can't be used from the RTFM application #[inline(always)] fn set(&self, value: u8) { self.inner.set(value) @@ -64,13 +68,12 @@ impl Priority { } } -#[cfg(feature = "nightly")] +// We newtype `core::mem::MaybeUninit` so the end-user doesn't need `#![feature(maybe_uninit)]` in +// their code pub struct MaybeUninit { - // we newtype so the end-user doesn't need `#![feature(maybe_uninit)]` in their code inner: core::mem::MaybeUninit, } -#[cfg(feature = "nightly")] impl MaybeUninit { pub const fn uninit() -> Self { MaybeUninit { @@ -86,64 +89,15 @@ impl MaybeUninit { self.inner.as_mut_ptr() } + pub unsafe fn read(&self) -> T { + self.inner.read() + } + pub fn write(&mut self, value: T) -> &mut T { self.inner.write(value) } } -#[cfg(not(feature = "nightly"))] -pub struct MaybeUninit { - value: Option, -} - -#[cfg(not(feature = "nightly"))] -const MSG: &str = - "you have hit a bug (UB) in RTFM implementation; try enabling this crate 'nightly' feature"; - -#[cfg(not(feature = "nightly"))] -impl MaybeUninit { - pub const fn uninit() -> Self { - MaybeUninit { value: None } - } - - pub fn as_ptr(&self) -> *const T { - if let Some(x) = self.value.as_ref() { - x - } else { - unreachable!(MSG) - } - } - - pub fn as_mut_ptr(&mut self) -> *mut T { - if let Some(x) = self.value.as_mut() { - x - } else { - unreachable!(MSG) - } - } - - pub unsafe fn get_ref(&self) -> &T { - if let Some(x) = self.value.as_ref() { - x - } else { - unreachable!(MSG) - } - } - - pub unsafe fn get_mut(&mut self) -> &mut T { - if let Some(x) = self.value.as_mut() { - x - } else { - unreachable!(MSG) - } - } - - pub fn write(&mut self, val: T) { - // NOTE(volatile) we have observed UB when this uses a plain `ptr::write` - unsafe { ptr::write_volatile(&mut self.value, Some(val)) } - } -} - #[inline(always)] pub fn assert_send() where @@ -160,19 +114,16 @@ where #[cfg(armv7m)] #[inline(always)] -pub unsafe fn claim( +pub unsafe fn lock( ptr: *mut T, priority: &Priority, ceiling: u8, nvic_prio_bits: u8, - f: F, -) -> R -where - F: FnOnce(&mut T) -> R, -{ + f: impl FnOnce(&mut T) -> R, +) -> R { let current = priority.get(); - if priority.get() < ceiling { + if current < ceiling { if ceiling == (1 << nvic_prio_bits) { priority.set(u8::MAX); let r = interrupt::free(|_| f(&mut *ptr)); @@ -193,19 +144,16 @@ where #[cfg(not(armv7m))] #[inline(always)] -pub unsafe fn claim( +pub unsafe fn lock( ptr: *mut T, priority: &Priority, ceiling: u8, _nvic_prio_bits: u8, - f: F, -) -> R -where - F: FnOnce(&mut T) -> R, -{ + f: impl FnOnce(&mut T) -> R, +) -> R { let current = priority.get(); - if priority.get() < ceiling { + if current < ceiling { priority.set(u8::MAX); let r = interrupt::free(|_| f(&mut *ptr)); priority.set(current); @@ -215,8 +163,7 @@ where } } -#[cfg(armv7m)] #[inline] -fn logical2hw(logical: u8, nvic_prio_bits: u8) -> u8 { +pub fn logical2hw(logical: u8, nvic_prio_bits: u8) -> u8 { ((1 << nvic_prio_bits) - logical) << (8 - nvic_prio_bits) } diff --git a/src/lib.rs b/src/lib.rs index b0bf6689e3..acd8d433fd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,8 @@ //! Real Time For the Masses (RTFM) framework for ARM Cortex-M microcontrollers //! +//! **HEADS UP** This is an **alpha** pre-release; there may be breaking changes in the API and +//! semantics before a proper release is made. +//! //! **IMPORTANT**: This crate is published as [`cortex-m-rtfm`] on crates.io but the name of the //! library is `rtfm`. //! @@ -7,7 +10,7 @@ //! //! The user level documentation can be found [here]. //! -//! [here]: https://japaric.github.io/cortex-m-rtfm/book/en/ +//! [here]: https://japaric.github.io/rtfm5/book/en/ //! //! Don't forget to check the documentation of the [`#[app]`] attribute, which is the main component //! of the framework. @@ -16,7 +19,7 @@ //! //! # Minimum Supported Rust Version (MSRV) //! -//! This crate is guaranteed to compile on stable Rust 1.31 (2018 edition) and up. It *might* +//! This crate is guaranteed to compile on stable Rust 1.36 (2018 edition) and up. It *might* //! compile on older versions but that may change in any new patch release. //! //! # Semantic Versioning @@ -36,12 +39,11 @@ //! [`Instant`]: struct.Instant.html //! [`Duration`]: struct.Duration.html //! -//! - `nightly`. Enabling this opt-in feature makes RTFM internally use the unstable -//! `core::mem::MaybeUninit` API and unstable `const_fn` language feature to reduce static memory -//! usage, runtime overhead and initialization overhead. This feature requires a nightly compiler -//! and may stop working at any time! +//! - `nightly`. Enabling this opt-in feature makes RTFM internally use the unstable `const_fn` +//! language feature to reduce static memory usage, runtime overhead and initialization overhead. +//! This feature requires a nightly compiler and may stop working at any time! -#![cfg_attr(feature = "nightly", feature(maybe_uninit))] +#![feature(maybe_uninit)] #![deny(missing_docs)] #![deny(warnings)] #![no_std] @@ -132,7 +134,7 @@ pub struct Instant(i32); impl Instant { /// IMPLEMENTATION DETAIL. DO NOT USE #[doc(hidden)] - pub fn artificial(timestamp: i32) -> Self { + pub unsafe fn artificial(timestamp: i32) -> Self { Instant(timestamp) } @@ -290,9 +292,7 @@ pub trait Mutex { type T; /// Creates a critical section and grants temporary access to the protected data - fn lock(&mut self, f: F) -> R - where - F: FnOnce(&mut Self::T) -> R; + fn lock(&mut self, f: impl FnOnce(&mut Self::T) -> R) -> R; } impl<'a, M> Mutex for &'a mut M @@ -301,10 +301,7 @@ where { type T = M::T; - fn lock(&mut self, f: F) -> R - where - F: FnOnce(&mut Self::T) -> R, - { + fn lock(&mut self, f: impl FnOnce(&mut M::T) -> R) -> R { (**self).lock(f) } } @@ -317,10 +314,7 @@ pub struct Exclusive<'a, T>(pub &'a mut T); impl<'a, T> Mutex for Exclusive<'a, T> { type T = T; - fn lock(&mut self, f: F) -> R - where - F: FnOnce(&mut Self::T) -> R, - { + fn lock(&mut self, f: impl FnOnce(&mut T) -> R) -> R { f(self.0) } } diff --git a/src/tq.rs b/src/tq.rs index 8d52051855..8ca1bd3f8e 100644 --- a/src/tq.rs +++ b/src/tq.rs @@ -3,7 +3,7 @@ use core::cmp::{self, Ordering}; use cortex_m::peripheral::{SCB, SYST}; use heapless::{binary_heap::Min, ArrayLength, BinaryHeap}; -use crate::{Instant, Mutex}; +use crate::Instant; pub struct TimerQueue where @@ -43,11 +43,39 @@ where } // set SysTick pending - (*SCB::ptr()).icsr.write(1 << 26); + SCB::set_pendst(); } self.queue.push_unchecked(nr); } + + #[inline] + pub fn dequeue(&mut self) -> Option<(T, u8)> { + if let Some(instant) = self.queue.peek().map(|p| p.instant) { + let diff = instant.0.wrapping_sub(Instant::now().0); + + if diff < 0 { + // task became ready + let nr = unsafe { self.queue.pop_unchecked() }; + + Some((nr.task, nr.index)) + } else { + // set a new timeout + const MAX: u32 = 0x00ffffff; + + self.syst.set_reload(cmp::min(MAX, diff as u32)); + + // start counting down from the new reload + self.syst.clear_current(); + + None + } + } else { + // the queue is empty + self.syst.disable_interrupt(); + None + } + } } pub struct NotReady @@ -87,49 +115,3 @@ where Some(self.cmp(&other)) } } - -#[inline(always)] -pub fn isr(mut tq: TQ, mut f: F) -where - TQ: Mutex>, - T: Copy + Send, - N: ArrayLength>, - F: FnMut(T, u8), -{ - loop { - // XXX does `#[inline(always)]` improve performance or not? - let next = tq.lock(#[inline(always)] - |tq| { - if let Some(instant) = tq.queue.peek().map(|p| p.instant) { - let diff = instant.0.wrapping_sub(Instant::now().0); - - if diff < 0 { - // task became ready - let m = unsafe { tq.queue.pop_unchecked() }; - - Some((m.task, m.index)) - } else { - // set a new timeout - const MAX: u32 = 0x00ffffff; - - tq.syst.set_reload(cmp::min(MAX, diff as u32)); - - // start counting down from the new reload - tq.syst.clear_current(); - - None - } - } else { - // the queue is empty - tq.syst.disable_interrupt(); - None - } - }); - - if let Some((task, index)) = next { - f(task, index) - } else { - return; - } - } -}