aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jorge Aparicio <jorge@japaric.io> 2019-04-21 20:02:59 +0200
committerGravatar Jorge Aparicio <jorge@japaric.io> 2019-05-01 20:49:25 +0200
commita452700628e352e6ac01da9e16223a47752ca860 (patch)
treec04a58222ba95e59d6b6013b5d2314068de6b1d0
parente6fb2f216fccc09d8e996525dcef3ffb2004f1ec (diff)
downloadrtic-a452700628e352e6ac01da9e16223a47752ca860.tar.gz
rtic-a452700628e352e6ac01da9e16223a47752ca860.tar.zst
rtic-a452700628e352e6ac01da9e16223a47752ca860.zip
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`.
-rw-r--r--CHANGELOG.md29
-rw-r--r--Cargo.toml11
-rw-r--r--macros/Cargo.toml6
-rw-r--r--macros/src/analyze.rs28
-rw-r--r--macros/src/check.rs21
-rw-r--r--macros/src/codegen.rs3876
-rw-r--r--macros/src/lib.rs6
-rw-r--r--macros/src/syntax.rs464
-rw-r--r--src/export.rs113
-rw-r--r--src/lib.rs32
-rw-r--r--src/tq.rs78
11 files changed, 2412 insertions, 2252 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index df4c674d..fb1102c0 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 b102ce78..236f3091 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 d891ba99..3771869c 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 cfd8ebc9..a47be779 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 4adc2c17..6ba7d375 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 4468216f..3fae75b7 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<Ident, Ident>;
-
-struct Context {
- // Alias
- #[cfg(feature = "timer-queue")]
- baseline: Ident,
- dispatchers: BTreeMap<u8, Dispatcher>,
- // Alias (`fn`)
- idle: Ident,
- // Alias (`fn`)
- init: Ident,
- // Alias
- priority: Ident,
- // For non-singletons this maps the resource name to its `static mut` variable name
- statics: Aliases,
- /// Task -> Alias (`struct`)
- resources: HashMap<Kind, Resources>,
- // Alias (`enum`)
- schedule_enum: Ident,
- // Task -> Alias (`fn`)
- schedule_fn: Aliases,
- tasks: BTreeMap<Ident, Task>,
- // Alias (`struct` / `static mut`)
- timer_queue: Ident,
- // Generator of Ident names or suffixes
- ident_gen: IdentGenerator,
-}
-
-struct Dispatcher {
- enum_: Ident,
- ready_queue: Ident,
-}
-
-struct Task {
- alias: Ident,
- free_queue: Ident,
- inputs: Ident,
- spawn_fn: Ident,
-
- #[cfg(feature = "timer-queue")]
- scheduleds: Ident,
-}
-
-impl Default for Context {
- fn default() -> Self {
- let mut ident_gen = IdentGenerator::new();
-
- Context {
- #[cfg(feature = "timer-queue")]
- baseline: ident_gen.mk_ident(None, false),
- dispatchers: BTreeMap::new(),
- idle: ident_gen.mk_ident(Some("idle"), false),
- init: ident_gen.mk_ident(Some("init"), false),
- priority: ident_gen.mk_ident(None, false),
- statics: Aliases::new(),
- resources: HashMap::new(),
- schedule_enum: ident_gen.mk_ident(None, false),
- schedule_fn: Aliases::new(),
- tasks: BTreeMap::new(),
- timer_queue: ident_gen.mk_ident(None, false),
- ident_gen,
- }
- }
-}
-
-struct Resources {
- alias: Ident,
- decl: proc_macro2::TokenStream,
-}
-
-pub fn app(app: &App, analysis: &Analysis) -> TokenStream {
- let mut ctxt = Context::default();
-
- let resources = resources(&mut ctxt, &app, analysis);
-
- let tasks = tasks(&mut ctxt, &app, analysis);
-
- let (dispatchers_data, dispatchers) = dispatchers(&mut ctxt, &app, analysis);
-
- let (init_fn, has_late_resources) = init(&mut ctxt, &app, analysis);
- let init_arg = if cfg!(feature = "timer-queue") {
- quote!(rtfm::Peripherals {
- CBP: p.CBP,
- CPUID: p.CPUID,
- DCB: &mut p.DCB,
- FPB: p.FPB,
- FPU: p.FPU,
- ITM: p.ITM,
- MPU: p.MPU,
- SCB: &mut p.SCB,
- TPIU: p.TPIU,
- })
- } else {
- quote!(rtfm::Peripherals {
- CBP: p.CBP,
- CPUID: p.CPUID,
- DCB: p.DCB,
- DWT: p.DWT,
- FPB: p.FPB,
- FPU: p.FPU,
- ITM: p.ITM,
- MPU: p.MPU,
- SCB: &mut p.SCB,
- SYST: p.SYST,
- TPIU: p.TPIU,
- })
- };
+pub fn app(name: &Ident, app: &App, analysis: &Analysis) -> TokenStream {
+ let (const_app_resources, mod_resources) = resources(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];
+ let (
+ const_app_exceptions,
+ exception_mods,
+ exception_locals,
+ exception_resources,
+ user_exceptions,
+ ) = exceptions(app, analysis);
- Some(quote!(#alias.write(res.#name);))
- } else {
- None
- }
- })
- .collect::<Vec<_>>();
+ let (
+ const_app_interrupts,
+ interrupt_mods,
+ interrupt_locals,
+ interrupt_resources,
+ user_interrupts,
+ ) = interrupts(app, analysis);
- quote!(
- let res = #init(#init_arg);
- #(#assigns)*
- )
- } else {
- quote!(#init(#init_arg);)
- };
+ let (const_app_tasks, task_mods, task_locals, task_resources, user_tasks) =
+ tasks(app, analysis);
- let post_init = post_init(&ctxt, &app, analysis);
+ let const_app_dispatchers = dispatchers(&app, analysis);
- let (idle_fn, idle_expr) = idle(&mut ctxt, &app, analysis);
+ let const_app_spawn = spawn(app, analysis);
- let exceptions = exceptions(&mut ctxt, app, analysis);
+ let const_app_tq = timer_queue(app, analysis);
- let (root_interrupts, scoped_interrupts) = interrupts(&mut ctxt, app, analysis);
+ let const_app_schedule = schedule(app);
- let spawn = spawn(&mut ctxt, app, analysis);
+ let assertion_stmts = assertions(app, analysis);
- let schedule = match () {
- #[cfg(feature = "timer-queue")]
- () => schedule(&ctxt, app),
- #[cfg(not(feature = "timer-queue"))]
- () => quote!(),
- };
+ let pre_init_stmts = pre_init(&app, analysis);
- let timer_queue = timer_queue(&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 pre_init = pre_init(&ctxt, &app, analysis);
+ let post_init_stmts = post_init(&app, analysis);
- let assertions = assertions(app, analysis);
+ let (const_app_idle, mod_idle, idle_locals, idle_resources, user_idle, call_idle) =
+ idle(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
- #(#tasks)*
+ #mod_init
- #init_fn
+ #idle_locals
- #idle_fn
+ #idle_resources
- #[export_name = "main"]
- #[allow(unsafe_code)]
- #[doc(hidden)]
- unsafe fn #main() -> ! {
- #assertions
+ #mod_idle
- rtfm::export::interrupt::disable();
+ #(#exception_locals)*
- #pre_init
+ #(#exception_resources)*
- #init_phase
+ #(#exception_mods)*
- #post_init
+ #(#interrupt_locals)*
- rtfm::export::interrupt::enable();
+ #(#interrupt_resources)*
- #idle_expr
- }
- )
- .into()
-}
+ #(#interrupt_mods)*
-fn resources(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream {
- let mut items = vec![];
- let mut module = vec![];
- for (name, res) in &app.resources {
- let cfgs = &res.cfgs;
- let attrs = &res.attrs;
- let mut_ = &res.mutability;
- let ty = &res.ty;
- let expr = &res.expr;
+ #(#task_locals)*
- if res.singleton {
- items.push(quote!(
- #(#attrs)*
- pub static #mut_ #name: #ty = #expr;
- ));
+ #(#task_resources)*
- 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),
- ))
- }
+ #(#task_mods)*
- 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();
- )
- }),
- );
+ /// Implementation details
+ const #name: () = {
+ // always include the device crate, which contains the vector table
+ use #device as _;
- 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 })
- };
+ #(#const_app_resources)*
- items.push(mk_resource(
- ctxt,
- cfgs,
- name,
- quote!(#ty),
- *ceiling,
- ptr,
- app,
- Some(&mut module),
- ));
- }
- }
+ #const_app_init
- ctxt.statics.insert(name.clone(), alias);
- }
- }
+ #const_app_idle
- if !module.is_empty() {
- items.push(quote!(
- /// Resource proxies
- pub mod resources {
- #(#module)*
- }
- ));
- }
+ #(#const_app_exceptions)*
- quote!(#(#items)*)
-}
+ #(#const_app_interrupts)*
-fn init(ctxt: &mut Context, app: &App, analysis: &Analysis) -> (proc_macro2::TokenStream, bool) {
- let attrs = &app.init.attrs;
- let locals = mk_locals(&app.init.statics, true);
- let stmts = &app.init.stmts;
- // TODO remove in v0.5.x
- let assigns = app
- .init
- .assigns
- .iter()
- .map(|assign| {
- let attrs = &assign.attrs;
- if app
- .resources
- .get(&assign.left)
- .map(|r| r.expr.is_none())
- .unwrap_or(false)
- {
- let alias = &ctxt.statics[&assign.left];
- let expr = &assign.right;
- quote!(
- #(#attrs)*
- unsafe { #alias.write(#expr); }
- )
- } else {
- let left = &assign.left;
- let right = &assign.right;
- quote!(
- #(#attrs)*
- #left = #right;
- )
- }
- })
- .collect::<Vec<_>>();
+ #(#const_app_dispatchers)*
- let prelude = prelude(
- ctxt,
- Kind::Init,
- &app.init.args.resources,
- &app.init.args.spawn,
- &app.init.args.schedule,
- app,
- 255,
- analysis,
- );
+ #(#const_app_tasks)*
- 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);
+ #(#const_app_spawn)*
- let fields = app
- .resources
- .iter()
- .filter_map(|(name, res)| {
- if res.expr.is_none() {
- let ty = &res.ty;
- Some(quote!(pub #name: #ty))
- } else {
- None
- }
- })
- .collect::<Vec<_>>();
+ #(#const_app_tq)*
- let late_resources = quote!(
- #[allow(non_snake_case)]
- pub struct #ident {
- #(#fields),*
- }
- );
+ #(#const_app_schedule)*
- (
- 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,
- );
+ #[no_mangle]
+ unsafe fn main() -> ! {
+ #(#assertion_stmts)*
- #[cfg(feature = "timer-queue")]
- let baseline = &ctxt.baseline;
- let baseline_let = match () {
- #[cfg(feature = "timer-queue")]
- () => quote!(let ref #baseline = rtfm::Instant::artificial(0);),
+ #(#pre_init_stmts)*
- #[cfg(not(feature = "timer-queue"))]
- () => quote!(),
- };
+ #call_init
- let start_let = match () {
- #[cfg(feature = "timer-queue")]
- () => quote!(
- #[allow(unused_variables)]
- let start = *#baseline;
- ),
- #[cfg(not(feature = "timer-queue"))]
- () => quote!(),
- };
+ #(#post_init_stmts)*
- 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)
+ #call_idle
}
- ),
- has_late_resources,
+ };
)
+ .into()
}
-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,
+/* Main functions */
+/// In this pass we generate a static variable and a resource proxy for each resource
+///
+/// If the user specified a resource like this:
+///
+/// ```
+/// #[rtfm::app(device = ..)]
+/// const APP: () = {
+/// static mut X: UserDefinedStruct = ();
+/// static mut Y: u64 = 0;
+/// static mut Z: u32 = 0;
+/// }
+/// ```
+///
+/// We'll generate code like this:
+///
+/// - `const_app`
+///
+/// ```
+/// const APP: () = {
+/// static mut X: MaybeUninit<UserDefinedStruct> = MaybeUninit::uninit();
+/// static mut Y: u64 = 0;
+/// static mut Z: u32 = 0;
+///
+/// impl<'a> Mutex for resources::X<'a> { .. }
+///
+/// impl<'a> Mutex for resources::Y<'a> { .. }
+///
+/// // but not for `Z` because it's not shared and thus requires no proxy
+/// };
+/// ```
+///
+/// - `mod_resources`
+///
+/// ```
+/// mod resources {
+/// pub struct X<'a> {
+/// priority: &'a Priority,
+/// }
+///
+/// impl<'a> X<'a> {
+/// pub unsafe fn new(priority: &'a Priority) -> Self {
+/// X { priority }
+/// }
+///
+/// pub unsafe fn priority(&self) -> &Priority {
+/// self.priority
+/// }
+/// }
+///
+/// // same thing for `Y`
+///
+/// // but not for `Z`
+/// }
+/// ```
+fn resources(
app: &App,
- late_resources: Option<Ident>,
-) -> proc_macro2::TokenStream {
- let mut items = vec![];
- let mut fields = vec![];
-
- let name = kind.ident();
- let priority = &ctxt.priority;
- let device = &app.args.device;
+ analysis: &Analysis,
+) -> (
+ // const_app
+ Vec<proc_macro2::TokenStream>,
+ // mod_resources
+ proc_macro2::TokenStream,
+) {
+ let mut const_app = vec![];
+ let mut mod_resources = vec![];
- 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,
- ));
- }
+ for (name, res) in &app.resources {
+ let cfgs = &res.cfgs;
+ let attrs = &res.attrs;
+ let ty = &res.ty;
- fields.push(quote!(
- /// Core (Cortex-M) peripherals
- pub core: rtfm::Peripherals<'a>,
- /// Device specific peripherals
- pub device: #device::Peripherals,
+ if let Some(expr) = res.expr.as_ref() {
+ const_app.push(quote!(
+ #(#attrs)*
+ #(#cfgs)*
+ static mut #name: #ty = #expr;
+ ));
+ } else {
+ const_app.push(quote!(
+ #(#attrs)*
+ #(#cfgs)*
+ static mut #name: rtfm::export::MaybeUninit<#ty> =
+ rtfm::export::MaybeUninit::uninit();
));
- 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 let Some(Ownership::Shared { ceiling }) = analysis.ownerships.get(name) {
+ let ptr = if res.expr.is_none() {
+ quote!(#name.as_mut_ptr())
+ } else {
+ quote!(&mut #name)
+ };
- if spawn {
- lt = Some(quote!('a));
+ mod_resources.push(quote!(
+ pub struct #name<'a> {
+ priority: &'a Priority,
+ }
- fields.push(quote!(
- /// Tasks that can be spawned from this context
- pub spawn: Spawn<'a>,
- ));
+ impl<'a> #name<'a> {
+ #[inline(always)]
+ pub unsafe fn new(priority: &'a Priority) -> Self {
+ #name { priority }
+ }
- 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,
+ #[inline(always)]
+ pub unsafe fn priority(&self) -> &Priority {
+ self.priority
+ }
}
));
- } else {
- let baseline_field = match () {
- #[cfg(feature = "timer-queue")]
- () => {
- let baseline = &ctxt.baseline;
- quote!(
- // NOTE this field is visible so we use a shared reference to make it
- // immutable
- #[doc(hidden)]
- pub #baseline: &'a rtfm::Instant,
- )
- }
- #[cfg(not(feature = "timer-queue"))]
- () => quote!(),
- };
- items.push(quote!(
- /// Tasks that can be spawned from this context
- #[derive(Clone, Copy)]
- pub struct Spawn<'a> {
- #baseline_field
- #[doc(hidden)]
- pub #priority: &'a rtfm::export::Priority,
- }
+ 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 mod_resources = if mod_resources.is_empty() {
+ quote!()
+ } else {
+ quote!(mod resources {
+ use rtfm::export::Priority;
- let doc = match kind {
- Kind::Exception(_) => "Exception handler",
- Kind::Idle => "Idle loop",
- Kind::Init => "Initialization function",
- Kind::Interrupt(_) => "Interrupt handler",
- Kind::Task(_) => "Software task",
+ #(#mod_resources)*
+ })
};
- 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)*
- }
- )
+ (const_app, mod_resources)
}
-/// 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,
+// 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,
- logical_prio: u8,
analysis: &Analysis,
-) -> proc_macro2::TokenStream {
- let mut items = vec![];
+) -> (
+ // const_app
+ Vec<proc_macro2::TokenStream>,
+ // exception_mods
+ Vec<proc_macro2::TokenStream>,
+ // exception_locals
+ Vec<proc_macro2::TokenStream>,
+ // exception_resources
+ Vec<proc_macro2::TokenStream>,
+ // user_exceptions
+ Vec<proc_macro2::TokenStream>,
+) {
+ let mut const_app = vec![];
+ let mut mods = vec![];
+ let mut locals_structs = vec![];
+ let mut resources_structs = vec![];
+ let mut user_code = vec![];
+
+ for (name, exception) in &app.exceptions {
+ let (let_instant, instant) = if cfg!(feature = "timer-queue") {
+ (
+ Some(quote!(let instant = rtfm::Instant::now();)),
+ Some(quote!(, instant)),
+ )
+ } else {
+ (None, None)
+ };
+ let priority = &exception.args.priority;
+ let symbol = exception.args.binds(name);
+ const_app.push(quote!(
+ #[allow(non_snake_case)]
+ #[no_mangle]
+ unsafe fn #symbol() {
+ const PRIORITY: u8 = #priority;
- let lt = if kind.runs_once() {
- quote!('static)
- } else {
- quote!('a)
- };
+ #let_instant
- 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
- ));
- }
- }
+ rtfm::export::run(PRIORITY, || {
+ crate::#name(
+ #name::Locals::new(),
+ #name::Context::new(&rtfm::export::Priority::new(PRIORITY) #instant)
+ )
+ });
+ }
+ ));
- 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 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 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())
- };
+ resources_structs.push(item);
- if exclusive {
- exprs.push(quote!(
- #(#cfgs)*
- #name: rtfm::Exclusive(#expr)
- ));
- } else {
- exprs.push(quote!(
- #(#cfgs)*
- #name: #expr
- ));
- }
- }
- }
+ const_app.push(constructor);
}
- let alias = ctxt.ident_gen.mk_ident(None, false);
- let unsafety = if needs_unsafe {
- Some(quote!(unsafe))
+ mods.push(module(
+ Kind::Exception(name.clone()),
+ (!exception.args.resources.is_empty(), needs_lt),
+ !exception.args.schedule.is_empty(),
+ !exception.args.spawn.is_empty(),
+ false,
+ app,
+ ));
+
+ let attrs = &exception.attrs;
+ let context = &exception.context;
+ let (locals, lets) = locals(Kind::Exception(name.clone()), &exception.statics);
+ locals_structs.push(locals);
+ let use_u32ext = if cfg!(feature = "timer-queue") {
+ Some(quote!(
+ use rtfm::U32Ext as _;
+ ))
} else {
None
};
-
- let defs = &defs;
- let doc = format!("`{}::Resources`", kind.ident().to_string());
- let decl = quote!(
- #[doc = #doc]
+ let stmts = &exception.stmts;
+ user_code.push(quote!(
+ #(#attrs)*
#[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,)* } };
- ));
+ fn #name(__locals: #name::Locals, #context: #name::Context) {
+ #use_u32ext
+ use rtfm::Mutex as _;
- ctxt.resources
- .insert(kind.clone(), Resources { alias, decl });
+ #(#lets;)*
- 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;
+ #(#stmts)*
}
-
- ctxt.schedule_fn
- .insert(task.clone(), ctxt.ident_gen.mk_ident(None, false));
- }
-
- items.push(quote!(
- #[allow(unused_imports)]
- use rtfm::U32Ext;
-
- #[allow(unused_variables)]
- let schedule = #module::Schedule { #priority };
));
}
- if items.is_empty() {
- quote!()
- } else {
- quote!(
- let ref #priority = unsafe { rtfm::export::Priority::new(#logical_prio) };
-
- #(#items)*
- )
- }
+ (
+ const_app,
+ mods,
+ locals_structs,
+ resources_structs,
+ user_code,
+ )
}
-fn idle(
- ctxt: &mut Context,
+// 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,
-) -> (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<proc_macro2::TokenStream>,
+ // interrupt_mods
+ Vec<proc_macro2::TokenStream>,
+ // interrupt_locals
+ Vec<proc_macro2::TokenStream>,
+ // interrupt_resources
+ Vec<proc_macro2::TokenStream>,
+ // user_exceptions
+ Vec<proc_macro2::TokenStream>,
+) {
+ let mut const_app = vec![];
+ let mut mods = vec![];
+ let mut locals_structs = vec![];
+ let mut resources_structs = vec![];
+ let mut user_code = vec![];
- let prelude = prelude(
- ctxt,
- Kind::Idle,
- &idle.args.resources,
- &idle.args.spawn,
- &idle.args.schedule,
- app,
- 0,
- analysis,
- );
-
- let module = module(
- ctxt,
- Kind::Idle,
- !idle.args.schedule.is_empty(),
- !idle.args.spawn.is_empty(),
- app,
- None,
- );
-
- let unsafety = &idle.unsafety;
- let idle = &ctxt.idle;
-
- (
- quote!(
- #module
-
- // unsafe trampoline to deter end-users from calling this non-reentrant function
- #(#attrs)*
- unsafe fn #idle() -> ! {
- #[inline(always)]
- #unsafety fn idle() -> ! {
- #(#locals)*
+ let device = &app.args.device;
+ for (name, interrupt) in &app.interrupts {
+ let (let_instant, instant) = if cfg!(feature = "timer-queue") {
+ (
+ Some(quote!(let instant = rtfm::Instant::now();)),
+ Some(quote!(, instant)),
+ )
+ } else {
+ (None, None)
+ };
+ let priority = &interrupt.args.priority;
+ let symbol = interrupt.args.binds(name);
+ const_app.push(quote!(
+ #[allow(non_snake_case)]
+ #[no_mangle]
+ unsafe fn #symbol() {
+ const PRIORITY: u8 = #priority;
- #prelude
+ #let_instant
- #(#stmts)*
- }
+ // check that this interrupt exists
+ let _ = #device::Interrupt::#symbol;
- idle()
- }
- ),
- quote!(#idle()),
- )
- } else {
- (
- quote!(),
- quote!(loop {
- rtfm::export::wfi();
- }),
- )
- }
-}
+ rtfm::export::run(PRIORITY, || {
+ crate::#name(
+ #name::Locals::new(),
+ #name::Context::new(&rtfm::export::Priority::new(PRIORITY) #instant)
+ )
+ });
+ }
+ ));
-fn exceptions(ctxt: &mut Context, app: &App, analysis: &Analysis) -> Vec<proc_macro2::TokenStream> {
- app.exceptions
- .iter()
- .map(|(ident, exception)| {
- let attrs = &exception.attrs;
- let stmts = &exception.stmts;
-
- let kind = Kind::Exception(ident.clone());
- let prelude = prelude(
- ctxt,
- kind.clone(),
- &exception.args.resources,
- &exception.args.spawn,
- &exception.args.schedule,
+ 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,
- exception.args.priority,
analysis,
);
- let module = module(
- ctxt,
- kind,
- !exception.args.schedule.is_empty(),
- !exception.args.spawn.is_empty(),
- app,
- None,
- );
-
- #[cfg(feature = "timer-queue")]
- let baseline = &ctxt.baseline;
- let baseline_let = match () {
- #[cfg(feature = "timer-queue")]
- () => quote!(let ref #baseline = rtfm::Instant::now();),
- #[cfg(not(feature = "timer-queue"))]
- () => quote!(),
- };
-
- let start_let = match () {
- #[cfg(feature = "timer-queue")]
- () => quote!(
- #[allow(unused_variables)]
- let start = *#baseline;
- ),
- #[cfg(not(feature = "timer-queue"))]
- () => quote!(),
- };
-
- let locals = mk_locals(&exception.statics, false);
- let symbol = exception.args.binds(ident).to_string();
- let alias = ctxt.ident_gen.mk_ident(None, false);
- let unsafety = &exception.unsafety;
- quote!(
- #module
-
- // unsafe trampoline to deter end-users from calling this non-reentrant function
- #[export_name = #symbol]
- #(#attrs)*
- unsafe fn #alias() {
- #[inline(always)]
- #unsafety fn exception() {
- #(#locals)*
-
- #baseline_let
+ resources_structs.push(item);
- #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,
- app,
- interrupt.args.priority,
- analysis,
- );
+ const_app.push(constructor);
+ }
- root.push(module(
- ctxt,
- kind,
+ 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,
- None,
));
- #[cfg(feature = "timer-queue")]
- let baseline = &ctxt.baseline;
- let baseline_let = match () {
- #[cfg(feature = "timer-queue")]
- () => quote!(let ref #baseline = rtfm::Instant::now();),
- #[cfg(not(feature = "timer-queue"))]
- () => quote!(),
- };
-
- let start_let = match () {
- #[cfg(feature = "timer-queue")]
- () => quote!(
- #[allow(unused_variables)]
- let start = *#baseline;
- ),
- #[cfg(not(feature = "timer-queue"))]
- () => quote!(),
+ let 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 = 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 (locals, lets) = locals(Kind::Interrupt(name.clone()), &interrupt.statics);
+ locals_structs.push(locals);
+ let stmts = &interrupt.stmts;
+ user_code.push(quote!(
#(#attrs)*
- #[export_name = #symbol]
- unsafe fn #alias() {
- #[inline(always)]
- #unsafety fn interrupt() {
- #(#locals)*
-
- #baseline_let
-
- #prelude
-
- #start_let
+ #[allow(non_snake_case)]
+ fn #name(__locals: #name::Locals, #context: #name::Context) {
+ #use_u32ext
+ use rtfm::Mutex as _;
- rtfm::export::run(move || {
- #(#stmts)*
- })
- }
+ #(#lets;)*
- 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 task we'll generate:
+//
+// - at the root of the crate:
+// - a ${name}Resources struct (maybe)
+// - a ${name}Locals struct
+//
+// - a module named after the task, see the `module` function for more details
+//
+// - hidden in `const APP`
+// - the ${name}Resources constructor
+// - an INPUTS buffer
+// - a free queue and a corresponding resource
+// - an INSTANTS buffer (if `timer-queue` is enabled)
+//
+// - the task handler specified by the user
+fn tasks(
+ app: &App,
+ analysis: &Analysis,
+) -> (
+ // const_app
+ Vec<proc_macro2::TokenStream>,
+ // task_mods
+ Vec<proc_macro2::TokenStream>,
+ // task_locals
+ Vec<proc_macro2::TokenStream>,
+ // task_resources
+ Vec<proc_macro2::TokenStream>,
+ // user_tasks
+ Vec<proc_macro2::TokenStream>,
+) {
+ let mut const_app = vec![];
+ let mut mods = vec![];
+ let mut locals_structs = vec![];
+ let mut resources_structs = vec![];
+ let mut user_code = vec![];
- // 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) = regroup_inputs(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 cap = analysis.capacities[name];
+ let cap_lit = mk_capacity_literal(cap);
+ let cap_ty = mk_typenum_capacity(cap, true);
- let scheduleds_static = match () {
- #[cfg(feature = "timer-queue")]
- () => {
- let scheduleds_symbol = format!("{}::SCHEDULED_TIMES::{}", name, scheduleds_alias);
+ let task_inputs = mk_inputs_ident(name);
+ let task_instants = mk_instants_ident(name);
+ let task_fq = mk_fq_ident(name);
- if cfg!(feature = "nightly") {
- let inits =
- (0..capacity).map(|_| quote!(rtfm::export::MaybeUninit::uninit()));
+ let elems = (0..cap)
+ .map(|_| quote!(rtfm::export::MaybeUninit::uninit()))
+ .collect::<Vec<_>>();
- quote!(
- #[doc = #scheduleds_symbol]
- static mut #scheduleds_alias:
- [rtfm::export::MaybeUninit<rtfm::Instant>; #capacity_lit] =
- [#(#inits),*];
- )
- } else {
- quote!(
- #[doc = #scheduleds_symbol]
- static mut #scheduleds_alias:
- rtfm::export::MaybeUninit<[rtfm::Instant; #capacity_lit]> =
- rtfm::export::MaybeUninit::uninit();
- )
- }
- }
- #[cfg(not(feature = "timer-queue"))]
- () => quote!(),
- };
+ 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<rtfm::Instant>; #cap_lit] =
+ [#(#elems,)*];
+ ));
+ }
- 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()));
+ const_app.push(quote!(
+ /// Buffer that holds the inputs of a task
+ static mut #task_inputs: [rtfm::export::MaybeUninit<#ty>; #cap_lit] =
+ [#(#elems,)*];
+ ));
- items.push(quote!(
- #[doc = #free_symbol]
- static mut #free_alias: rtfm::export::FreeQueue<#capacity_ty> = unsafe {
- rtfm::export::FreeQueue::new_sc()
+ 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()
};
-
- #[doc = #inputs_symbol]
- static mut #inputs_alias: [rtfm::export::MaybeUninit<#ty>; #capacity_lit] =
- [#(#inits),*];
));
- } else {
- items.push(quote!(
- #[doc = #free_symbol]
- static mut #free_alias: rtfm::export::MaybeUninit<
- rtfm::export::FreeQueue<#capacity_ty>
- > = rtfm::export::MaybeUninit::uninit();
- #[doc = #inputs_symbol]
- static mut #inputs_alias: rtfm::export::MaybeUninit<[#ty; #capacity_lit]> =
+ quote!(&mut #task_fq)
+ } else {
+ const_app.push(quote!(
+ #[doc = #doc]
+ static mut #task_fq: rtfm::export::MaybeUninit<#fq_ty> =
rtfm::export::MaybeUninit::uninit();
-
));
- }
- items.push(quote!(
- #resource
+ quote!(#task_fq.as_mut_ptr())
+ };
- #scheduleds_static
- ));
+ if let Some(ceiling) = analysis.free_queues.get(name) {
+ const_app.push(quote!(struct #task_fq<'a> {
+ priority: &'a rtfm::export::Priority,
+ }));
- ctxt.tasks.insert(
- name.clone(),
- Task {
- alias: task_alias,
- free_queue: free_alias,
- inputs: inputs_alias,
- spawn_fn: ctxt.ident_gen.mk_ident(None, false),
-
- #[cfg(feature = "timer-queue")]
- scheduleds: scheduleds_alias,
- },
- );
- }
+ const_app.push(impl_mutex(app, &[], false, &task_fq, fq_ty, *ceiling, ptr));
+ }
- // 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 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,
+ );
- let scheduled_let = match () {
- #[cfg(feature = "timer-queue")]
- () => {
- let baseline = &ctxt.baseline;
- quote!(let scheduled = *#baseline;)
- }
- #[cfg(not(feature = "timer-queue"))]
- () => quote!(),
- };
+ resources_structs.push(item);
- let prelude = prelude(
- ctxt,
- Kind::Task(name.clone()),
- &task.args.resources,
- &task.args.spawn,
- &task.args.schedule,
- app,
- task.args.priority,
- analysis,
- );
+ const_app.push(constructor);
+ }
- items.push(module(
- ctxt,
+ 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)*
-
- #prelude
+ #[allow(non_snake_case)]
+ fn #name(__locals: #name::Locals, #context: #name::Context #(,#inputs)*) {
+ use rtfm::Mutex as _;
+ #use_u32ext
- #scheduled_let
+ #(#lets;)*
- #(#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<proc_macro2::TokenStream> {
+ 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::<Vec<_>>();
- let symbol = format!("P{}::READY_QUEUE::{}", level, ready_alias);
- let e = quote!(rtfm::export);
- let ty = quote!(#e::ReadyQueue<#enum_alias, #capacity>);
- let ceiling = *analysis.ready_queues.get(&level).unwrap_or(&0);
- let resource = mk_resource(
- ctxt,
- &[],
- &ready_alias,
- ty.clone(),
- ceiling,
- if cfg!(feature = "nightly") {
- quote!(&mut #ready_alias)
- } else {
- quote!(#ready_alias.get_mut())
- },
- app,
- None,
- );
- if cfg!(feature = "nightly") {
- data.push(quote!(
- #[doc = #symbol]
- static mut #ready_alias: #ty = unsafe { #e::ReadyQueue::new_sc() };
- ));
- } else {
- data.push(quote!(
- #[doc = #symbol]
- static mut #ready_alias: #e::MaybeUninit<#ty> = #e::MaybeUninit::uninit();
- ));
- }
- data.push(quote!(
- #[allow(dead_code)]
+ let doc = format!(
+ "Software tasks to be dispatched at priority level {}",
+ level
+ );
+ 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;
-
- let task__ = &app.tasks[task];
- let pats = tuple_pat(&task__.inputs);
- let cfgs = &task__.cfgs;
-
- let baseline_let;
- let call;
- match () {
- #[cfg(feature = "timer-queue")]
- () => {
- let scheduleds = &task_.scheduleds;
- let scheduled = if cfg!(feature = "nightly") {
- quote!(#scheduleds.get_unchecked(usize::from(index)).as_ptr())
- } else {
- quote!(#scheduleds.get_ref().get_unchecked(usize::from(index)))
- };
+ .map(|name| {
+ let task = &app.tasks[name];
+ let cfgs = &task.cfgs;
+ let (_, tupled, pats, _) = regroup_inputs(&task.inputs);
- 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 inputs = mk_inputs_ident(name);
+ let fq = mk_fq_ident(name);
+
+ 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::<Vec<_>>();
+ 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<proc_macro2::TokenStream> {
let mut items = vec![];
- // Generate `spawn` functions
- let device = &app.args.device;
- let priority = &ctxt.priority;
- #[cfg(feature = "timer-queue")]
- let baseline = &ctxt.baseline;
- for (name, task) in &ctxt.tasks {
- let alias = &task.spawn_fn;
- let task_ = &app.tasks[name];
- let cfgs = &task_.cfgs;
- let free = &task.free_queue;
- let level = task_.args.priority;
- let dispatcher = &ctxt.dispatchers[&level];
- let ready = &dispatcher.ready_queue;
- let enum_ = &dispatcher.enum_;
- let dispatcher = &analysis.dispatchers[&level].interrupt;
- let inputs = &task.inputs;
- let args = &task_.inputs;
- let ty = tuple_ty(args);
- let pats = tuple_pat(args);
-
- let scheduleds_write = match () {
- #[cfg(feature = "timer-queue")]
- () => {
- let scheduleds = &ctxt.tasks[name].scheduleds;
- if cfg!(feature = "nightly") {
- quote!(
- ptr::write(
- #scheduleds.get_unchecked_mut(usize::from(index)).as_mut_ptr(),
- #baseline,
- );
- )
- } else {
- quote!(
- ptr::write(
- #scheduleds.get_mut().get_unchecked_mut(usize::from(index)),
- #baseline,
- );
- )
- }
- }
- #[cfg(not(feature = "timer-queue"))]
- () => quote!(),
- };
-
- let baseline_arg = match () {
- #[cfg(feature = "timer-queue")]
- () => quote!(#baseline: rtfm::Instant,),
- #[cfg(not(feature = "timer-queue"))]
- () => quote!(),
- };
-
- let input = if cfg!(feature = "nightly") {
- quote!(#inputs.get_unchecked_mut(usize::from(index)).as_mut_ptr())
- } else {
- quote!(#inputs.get_mut().get_unchecked_mut(usize::from(index)))
- };
- items.push(quote!(
- #[inline(always)]
- #(#cfgs)*
- unsafe fn #alias(
- #baseline_arg
- #priority: &rtfm::export::Priority,
- #(#args,)*
- ) -> Result<(), #ty> {
- use core::ptr;
-
- use rtfm::Mutex;
-
- if let Some(index) = (#free { #priority }).lock(|f| f.split().1.dequeue()) {
- ptr::write(#input, (#pats));
- #scheduleds_write
-
- #ready { #priority }.lock(|rq| {
- rq.split().0.enqueue_unchecked((#enum_::#name, index))
- });
-
- rtfm::pend(#device::Interrupt::#dispatcher);
-
- Ok(())
- } else {
- Err((#pats))
- }
- }
- ))
- }
-
- // 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,)
- }
- }
- #[cfg(not(feature = "timer-queue"))]
- () => quote!(),
- };
- methods.push(quote!(
- #[allow(unsafe_code)]
- #[inline]
- #(#cfgs)*
- pub fn #task(&self, #(#inputs,)*) -> Result<(), #ty> {
- unsafe { #alias(#instant &self.#priority, #pats) }
- }
- ));
- }
- items.push(quote!(
- impl<'a> #name::Spawn<'a> {
- #(#methods)*
- }
- ));
- }
+ 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);
- quote!(#(#items)*)
-}
+ if spawner_is_init {
+ // `init` uses a special spawn implementation; it doesn't use the `spawn_${name}`
+ // functions which are shared by other contexts
-#[cfg(feature = "timer-queue")]
-fn schedule(ctxt: &Context, app: &App) -> proc_macro2::TokenStream {
- let mut items = vec![];
+ let body = mk_spawn_body(&spawner, &name, app, analysis);
- // 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 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);
- 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,
- };
+ if !seen.contains(name) {
+ // generate a `spawn_${name}` function
+ seen.insert(name);
- ({#timer_queue { #priority }}).lock(|tq| tq.enqueue_unchecked(nr));
+ 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
+ }
+ ));
+ }
- Ok(())
+ 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 {
- Err((#pats))
- }
+ (None, None)
+ };
+ methods.push(quote!(
+ #(#cfgs)*
+ #[inline(always)]
+ fn #name(&self #(,#args)*) -> Result<(), #ty> {
+ unsafe {
+ #let_instant
+ #spawn(self.priority() #instant #(,#untupled)*)
+ }
+ }
+ ));
}
- ))
- }
-
- // 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) }
- }
- ));
}
+ let lt = if spawner_is_init {
+ None
+ } else {
+ Some(quote!('a))
+ };
items.push(quote!(
- impl<'a> #name::Schedule<'a> {
+ impl<#lt> #spawner::Spawn<#lt> {
#(#methods)*
}
));
}
- quote!(#(#items)*)
+ items
}
-fn timer_queue(ctxt: &mut Context, app: &App, analysis: &Analysis) -> 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<proc_macro2::TokenStream> {
+ let mut items = vec![];
+
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,389 +975,1485 @@ fn timer_queue(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro
)
})
.collect::<Vec<_>>();
- 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::TimerQueue<#enum_, #cap>> =
- rtfm::export::MaybeUninit::uninit();
- ));
- } else {
- items.push(quote!(
- #[doc = #symbol]
- static mut #tq:
- rtfm::export::MaybeUninit<rtfm::export::TimerQueue<#enum_, #cap>> =
- rtfm::export::MaybeUninit::uninit();
- ));
- }
+ let ty = quote!(rtfm::export::TimerQueue<T, #cap>);
+ 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(mk_resource(
- ctxt,
+ items.push(impl_mutex(
+ app,
&[],
- tq,
- quote!(rtfm::export::TimerQueue<#enum_, #cap>),
+ false,
+ &Ident::new("TQ", Span::call_site()),
+ ty,
analysis.timer_queue.ceiling,
- quote!(&mut *#tq.as_mut_ptr()),
- app,
- None,
+ 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::<Vec<_>>();
- 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;
-
- let ref #priority = rtfm::export::Priority::new(#logical_prio);
-
- rtfm::export::run(|| {
- rtfm::export::sys_tick(#tq { #priority }, |task, index| {
+ /// The system timer
+ #[no_mangle]
+ unsafe fn SysTick() {
+ use rtfm::Mutex as _;
+
+ /// System timer priority
+ const PRIORITY: u8 = #priority;
+
+ rtfm::export::run(PRIORITY, || {
+ while let Some((task, index)) = (TQ {
+ // NOTE dynamic priority is always the static priority at this point
+ priority: &rtfm::export::Priority::new(PRIORITY),
+ })
+ // NOTE `inline(always)` produces faster and smaller code
+ .lock(#[inline(always)]
+ |tq| tq.dequeue())
+ {
match task {
#(#arms)*
}
- });
- })
+ }
+ });
}
));
- 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<proc_macro2::TokenStream> {
+ let mut items = vec![];
+ if !cfg!(feature = "timer-queue") {
+ return items;
+ }
- 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());))
+ let mut seen = BTreeSet::new();
+ for (scheduler, schedulees) in app.schedule_callers() {
+ if schedulees.is_empty() {
+ continue;
}
- #[cfg(feature = "timer-queue")]
- for task in ctxt.tasks.values() {
- let scheduleds = &task.scheduleds;
- exprs.push(quote!(#scheduleds.write(core::mem::uninitialized());))
+ 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)*)
+ }
+ ));
+ }
}
- // 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());))
+ let lt = if scheduler_is_init {
+ None
+ } else {
+ Some(quote!('a))
+ };
+ items.push(quote!(
+ impl<#lt> #scheduler::Schedule<#lt> {
+ #(#methods)*
+ }
+ ));
+ }
+
+ items
+}
+
+/// Generates `Send` / `Sync` compile time checks
+fn assertions(app: &App, analysis: &Analysis) -> Vec<proc_macro2::TokenStream> {
+ let mut stmts = vec![];
+
+ for ty in &analysis.assert_sync {
+ stmts.push(quote!(rtfm::export::assert_sync::<#ty>();));
+ }
+
+ for task in &analysis.tasks_assert_send {
+ let (_, _, _, ty) = regroup_inputs(&app.tasks[task].inputs);
+ stmts.push(quote!(rtfm::export::assert_send::<#ty>();));
+ }
+
+ // all late resources need to be `Send`
+ for ty in &analysis.resources_assert_send {
+ stmts.push(quote!(rtfm::export::assert_send::<#ty>();));
+ }
+
+ stmts
+}
+
+/// Generates code that we must run before `init` runs. See comments inside
+fn pre_init(app: &App, analysis: &Analysis) -> Vec<proc_macro2::TokenStream> {
+ let mut stmts = vec![];
+
+ stmts.push(quote!(rtfm::export::interrupt::disable();));
+
+ // these won't be required once we have better `const fn` on stable (or const generics)
+ if !cfg!(feature = "nightly") {
+ // 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());))
}
- // these are `MaybeUninit` `FreeQueue`s
- for task in ctxt.tasks.values() {
- let fq = &task.free_queue;
- exprs.push(quote!(#fq.write(rtfm::export::FreeQueue::new_sc());))
+ // initialize `MaybeUninit` `FreeQueue`s
+ for name in app.tasks.keys() {
+ let fq = mk_fq_ident(name);
+
+ stmts.push(quote!(
+ let fq = #fq.write(rtfm::export::FreeQueue::u8_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<proc_macro2::TokenStream>,
+ // mod_init
+ proc_macro2::TokenStream,
+ // init_locals
+ proc_macro2::TokenStream,
+ // init_resources
+ Option<proc_macro2::TokenStream>,
+ // init_late_resources
+ Option<proc_macro2::TokenStream>,
+ // user_init
+ proc_macro2::TokenStream,
+ // call_init
+ proc_macro2::TokenStream,
+) {
+ let mut needs_lt = false;
+ let mut const_app = None;
+ let mut init_resources = None;
+ if !app.init.args.resources.is_empty() {
+ let (item, constructor) = resources_struct(Kind::Init, 0, &mut needs_lt, app, analysis);
+
+ init_resources = Some(item);
+ const_app = Some(constructor);
+ }
+
+ let core = if cfg!(feature = "timer-queue") {
+ quote!(rtfm::Peripherals {
+ CBP: core.CBP,
+ CPUID: core.CPUID,
+ DCB: &mut core.DCB,
+ FPB: core.FPB,
+ FPU: core.FPU,
+ ITM: core.ITM,
+ MPU: core.MPU,
+ SCB: &mut core.SCB,
+ TPIU: core.TPIU,
+ })
+ } else {
+ quote!(rtfm::Peripherals {
+ CBP: core.CBP,
+ CPUID: core.CPUID,
+ DCB: core.DCB,
+ DWT: core.DWT,
+ FPB: core.FPB,
+ FPU: core.FPU,
+ ITM: core.ITM,
+ MPU: core.MPU,
+ SCB: &mut core.SCB,
+ SYST: core.SYST,
+ TPIU: core.TPIU,
+ })
+ };
+
+ let call_init = quote!(let late = init(init::Locals::new(), init::Context::new(#core)););
+
+ let late_fields = app
+ .resources
+ .iter()
+ .filter_map(|(name, res)| {
+ if res.expr.is_none() {
+ let ty = &res.ty;
+
+ Some(quote!(pub #name: #ty))
+ } else {
+ None
+ }
+ })
+ .collect::<Vec<_>>();
+
+ let attrs = &app.init.attrs;
+ let has_late_resources = !late_fields.is_empty();
+ let (ret, init_late_resources) = if has_late_resources {
+ (
+ Some(quote!(-> init::LateResources)),
+ Some(quote!(
+ /// Resources initialized at runtime
+ #[allow(non_snake_case)]
+ pub struct initLateResources {
+ #(#late_fields),*
+ }
+ )),
+ )
+ } else {
+ (None, None)
+ };
+ let context = &app.init.context;
+ let use_u32ext = if cfg!(feature = "timer-queue") {
+ Some(quote!(
+ use rtfm::U32Ext as _;
+ ))
+ } else {
+ None
+ };
+ let (locals_struct, lets) = locals(Kind::Init, &app.init.statics);
+ let stmts = &app.init.stmts;
+ let user_init = quote!(
+ #(#attrs)*
+ #[allow(non_snake_case)]
+ fn init(__locals: init::Locals, #context: init::Context) #ret {
+ #use_u32ext
+
+ #(#lets;)*
+
+ #(#stmts)*
+ }
+ );
+
+ let mod_init = module(
+ Kind::Init,
+ (!app.init.args.resources.is_empty(), needs_lt),
+ !app.init.args.schedule.is_empty(),
+ !app.init.args.spawn.is_empty(),
+ has_late_resources,
+ app,
+ );
+
+ (
+ const_app,
+ mod_init,
+ locals_struct,
+ init_resources,
+ init_late_resources,
+ user_init,
+ call_init,
)
}
-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<proc_macro2::TokenStream> {
+ 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),
+ );));
+ }
+
+ 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();));
}
- quote!(#(#items;)*)
+ stmts.push(quote!(rtfm::export::interrupt::enable();));
+
+ stmts
}
-fn mk_resource(
- ctxt: &Context,
- cfgs: &[Attribute],
- struct_: &Ident,
- ty: proc_macro2::TokenStream,
- ceiling: u8,
- ptr: proc_macro2::TokenStream,
+// 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,
- module: Option<&mut Vec<proc_macro2::TokenStream>>,
-) -> proc_macro2::TokenStream {
- let priority = &ctxt.priority;
- let device = &app.args.device;
+ analysis: &Analysis,
+) -> (
+ // const_app_idle
+ Option<proc_macro2::TokenStream>,
+ // mod_idle
+ Option<proc_macro2::TokenStream>,
+ // idle_locals
+ Option<proc_macro2::TokenStream>,
+ // idle_resources
+ Option<proc_macro2::TokenStream>,
+ // user_idle
+ Option<proc_macro2::TokenStream>,
+ // call_idle
+ proc_macro2::TokenStream,
+) {
+ if let Some(idle) = app.idle.as_ref() {
+ let mut needs_lt = false;
+ let mut const_app = None;
+ let mut idle_resources = None;
- let mut items = vec![];
+ if !idle.args.resources.is_empty() {
+ let (item, constructor) = resources_struct(Kind::Idle, 0, &mut needs_lt, app, analysis);
- 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,
- }
+ idle_resources = Some(item);
+ const_app = Some(constructor);
+ }
+
+ let call_idle = quote!(idle(
+ idle::Locals::new(),
+ idle::Context::new(&rtfm::export::Priority::new(0))
));
- quote!(resources::#struct_)
+ 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 {
- items.push(quote!(
- #(#cfgs)*
- struct #struct_<'a> {
- #priority: &'a rtfm::export::Priority,
+ (
+ 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 ()>
));
- quote!(#struct_)
+ 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)
+}
- items.push(quote!(
+/// Creates a `Mutex` implementation
+fn impl_mutex(
+ app: &App,
+ cfgs: &[Attribute],
+ resources_prefix: bool,
+ name: &Ident,
+ ty: proc_macro2::TokenStream,
+ ceiling: u8,
+ ptr: proc_macro2::TokenStream,
+) -> proc_macro2::TokenStream {
+ let path = if resources_prefix {
+ quote!(resources::#name)
+ } else {
+ quote!(#name)
+ };
+
+ let priority = if resources_prefix {
+ quote!(self.priority())
+ } else {
+ quote!(self.priority)
+ };
+
+ let device = &app.args.device;
+ quote!(
#(#cfgs)*
impl<'a> rtfm::Mutex for #path<'a> {
type T = #ty;
- #[inline]
- fn lock<R, F>(&mut self, f: F) -> R
- where
- F: FnOnce(&mut Self::T) -> R,
- {
+ #[inline(always)]
+ fn lock<R>(&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)*)
+ )
}
-fn mk_capacity_literal(capacity: u8) -> LitInt {
- LitInt::new(u64::from(capacity), IntSuffix::None, Span::call_site())
-}
+/// Creates a `Locals` struct and related code. This returns
+///
+/// - `locals`
+///
+/// ```
+/// pub struct Locals<'a> {
+/// #[cfg(never)]
+/// pub X: &'a mut X,
+/// __marker__: PhantomData<&'a mut ()>,
+/// }
+/// ```
+///
+/// - `lt`
+///
+/// ```
+/// 'a
+/// ```
+///
+/// - `lets`
+///
+/// ```
+/// #[cfg(never)]
+/// let X = __locals.X
+/// ```
+fn locals(
+ kind: Kind,
+ statics: &BTreeMap<Ident, Static>,
+) -> (
+ // locals
+ proc_macro2::TokenStream,
+ // lets
+ Vec<proc_macro2::TokenStream>,
+) {
+ let runs_once = kind.runs_once();
+ let ident = kind.locals_ident();
-fn mk_typenum_capacity(capacity: u8, power_of_two: bool) -> proc_macro2::TokenStream {
- let capacity = if power_of_two {
- capacity
- .checked_next_power_of_two()
- .expect("capacity.next_power_of_two()")
- } else {
- capacity
- };
+ let 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 ident = Ident::new(&format!("U{}", capacity), Span::call_site());
+ 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
+ ));
+ }
- quote!(rtfm::export::consts::#ident)
-}
+ if lt.is_some() {
+ fields.push(quote!(__marker__: core::marker::PhantomData<&'a mut ()>));
+ values.push(quote!(__marker__: core::marker::PhantomData));
+ }
-struct IdentGenerator {
- call_count: u32,
- rng: rand::rngs::SmallRng,
+ 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)
}
-impl IdentGenerator {
- fn new() -> IdentGenerator {
- let elapsed = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
+/// 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 secs = elapsed.as_secs();
- let nanos = elapsed.subsec_nanos();
+ 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
+ ));
- let mut seed: [u8; 16] = [0; 16];
+ values.push(quote!(start: rtfm::Instant::artificial(0)));
+ }
- for (i, v) in seed.iter_mut().take(8).enumerate() {
- *v = ((secs >> (i * 8)) & 0xFF) as u8
+ 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));
}
- for (i, v) in seed.iter_mut().skip(8).take(4).enumerate() {
- *v = ((nanos >> (i * 8)) & 0xFF) as u8
+ 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;
+ }
}
- let rng = rand::rngs::SmallRng::from_seed(seed);
+ 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));
- IdentGenerator { call_count: 0, rng }
+ needs_instant = true;
+ }
+ }
}
- fn mk_ident(&mut self, name: Option<&str>, random: bool) -> Ident {
- let s = if let Some(name) = name {
- format!("{}_", name)
+ 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 {
- "__rtfm_internal_".to_string()
+ 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 ()>,
+ }
+ ));
- let mut s = format!("{}{}", s, self.call_count);
- self.call_count += 1;
+ fields.push(quote!(
+ #[doc = #doc]
+ pub schedule: Schedule
+ ));
- if random {
- s.push('_');
+ 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>
+ ));
- for i in 0..4 {
- if i == 0 || self.rng.gen() {
- s.push(('a' as u8 + self.rng.gen::<u8>() % 25) as char)
+ 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 {
- s.push(('0' as u8 + self.rng.gen::<u8>() % 10) as char)
+ 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,)*
}
}
}
+ ));
- Ident::new(&s, Span::call_site())
+ if !items.is_empty() {
+ quote!(
+ #[allow(non_snake_case)]
+ #[doc = #doc]
+ pub mod #name {
+ #(#items)*
+ }
+ )
+ } else {
+ quote!()
}
}
-// `once = true` means that these locals will be called from a function that will run *once*
-fn mk_locals(locals: &BTreeMap<Ident, Static>, once: bool) -> proc_macro2::TokenStream {
- let lt = if once { Some(quote!('static)) } else { None };
+/// 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 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;
+ let spawnee = &app.tasks[name];
+ let priority = spawnee.args.priority;
+ let dispatcher = &analysis.dispatchers[&priority].interrupt;
- quote!(
- #[allow(non_snake_case)]
- #(#cfgs)*
- let #ident: &#lt mut #ty = {
- #(#attrs)*
- #(#cfgs)*
- static mut #ident: #ty = #expr;
+ let (_, tupled, _, _) = regroup_inputs(&spawnee.inputs);
- unsafe { &mut #ident }
- };
+ 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));),
)
- })
- .collect::<Vec<_>>();
+ } 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 _;
- quote!(#(#locals)*)
+ 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)
+ }
+ }
+ )
}
-fn tuple_pat(inputs: &[ArgCaptured]) -> proc_macro2::TokenStream {
- if inputs.len() == 1 {
- let pat = &inputs[0].pat;
- quote!(#pat)
+/// 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 {
- let pats = inputs.iter().map(|i| &i.pat).collect::<Vec<_>>();
+ (
+ quote!((#fq { priority }).lock(|fq| fq.split().1.dequeue())),
+ quote!((TQ { priority }).lock(|tq| tq.enqueue_unchecked(nr));),
+ )
+ };
- quote!(#(#pats,)*)
- }
+ 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())
}
-fn tuple_ty(inputs: &[ArgCaptured]) -> proc_macro2::TokenStream {
+/// e.g. `4u8` -> `U4`
+fn mk_typenum_capacity(capacity: u8, power_of_two: bool) -> proc_macro2::TokenStream {
+ let capacity = if power_of_two {
+ capacity
+ .checked_next_power_of_two()
+ .expect("capacity.next_power_of_two()")
+ } else {
+ capacity
+ };
+
+ let ident = Ident::new(&format!("U{}", capacity), Span::call_site());
+
+ quote!(rtfm::export::consts::#ident)
+}
+
+/// e.g. `foo` -> `foo_INPUTS`
+fn mk_inputs_ident(base: &Ident) -> Ident {
+ Ident::new(&format!("{}_INPUTS", base), Span::call_site())
+}
+
+/// e.g. `foo` -> `foo_INSTANTS`
+fn mk_instants_ident(base: &Ident) -> Ident {
+ Ident::new(&format!("{}_INSTANTS", base), Span::call_site())
+}
+
+/// e.g. `foo` -> `foo_FQ`
+fn mk_fq_ident(base: &Ident) -> Ident {
+ Ident::new(&format!("{}_FQ", base), Span::call_site())
+}
+
+/// e.g. `3` -> `RQ3`
+fn mk_rq_ident(level: u8) -> Ident {
+ Ident::new(&format!("RQ{}", level), Span::call_site())
+}
+
+/// e.g. `3` -> `T3`
+fn mk_t_ident(level: u8) -> Ident {
+ Ident::new(&format!("T{}", level), Span::call_site())
+}
+
+fn mk_spawn_ident(task: &Ident) -> Ident {
+ Ident::new(&format!("spawn_{}", task), Span::call_site())
+}
+
+fn mk_schedule_ident(task: &Ident) -> Ident {
+ Ident::new(&format!("schedule_{}", task), Span::call_site())
+}
+
+// Regroups a task inputs
+//
+// e.g. &[`input: Foo`], &[`mut x: i32`, `ref y: i64`]
+fn regroup_inputs(
+ inputs: &[ArgCaptured],
+) -> (
+ // args e.g. &[`_0`], &[`_0: i32`, `_1: i64`]
+ Vec<proc_macro2::TokenStream>,
+ // tupled e.g. `_0`, `(_0, _1)`
+ proc_macro2::TokenStream,
+ // untupled e.g. &[`_0`], &[`_0`, `_1`]
+ Vec<proc_macro2::TokenStream>,
+ // ty e.g. `Foo`, `(i32, i64)`
+ proc_macro2::TokenStream,
+) {
if inputs.len() == 1 {
let ty = &inputs[0].ty;
- quote!(#ty)
+
+ (
+ vec![quote!(_0: #ty)],
+ quote!(_0),
+ vec![quote!(_0)],
+ quote!(#ty),
+ )
} else {
- let tys = inputs.iter().map(|i| &i.ty).collect::<Vec<_>>();
+ let mut args = vec![];
+ let mut pats = vec![];
+ let mut tys = vec![];
- quote!((#(#tys,)*))
+ 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 c8d9fee1..441d6b5e 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 228d9588..c6814d5f 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<Ident, FreeInterrupt>;
pub struct Idle {
pub args: IdleArgs,
pub attrs: Vec<Attribute>,
- pub unsafety: Option<Token![unsafe]>,
+ pub context: Pat,
pub statics: BTreeMap<Ident, Static>,
pub stmts: Vec<Stmt>,
}
@@ -444,34 +444,29 @@ pub type IdleArgs = InitArgs;
impl Idle {
fn check(args: IdleArgs, item: ItemFn) -> parse::Result<Self> {
- 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<Attribute>,
- pub left: Ident,
- pub right: Box<Expr>,
-}
-
pub struct Init {
pub args: InitArgs,
pub attrs: Vec<Attribute>,
- pub unsafety: Option<Token![unsafe]>,
pub statics: BTreeMap<Ident, Static>,
+ pub context: Pat,
pub stmts: Vec<Stmt>,
- // TODO remove in v0.5.x
- pub assigns: Vec<Assign>,
pub returns_late_resources: bool,
+ pub span: Span,
}
impl Init {
fn check(args: InitArgs, item: ItemFn) -> parse::Result<Self> {
- 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<Attribute>,
- pub unsafety: Option<Token![unsafe]>,
pub statics: BTreeMap<Ident, Static>,
+ pub context: Pat,
pub stmts: Vec<Stmt>,
}
@@ -770,61 +738,67 @@ impl Parse for ExceptionArgs {
impl Exception {
fn check(args: ExceptionArgs, item: ItemFn) -> parse::Result<Self> {
- 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);
-
- if !valid_signature {
- return Err(parse::Error::new(
- item.span(),
- "`exception` handlers must have type signature `[unsafe] fn()`",
- ));
- }
+ let valid_signature =
+ check_signature(&item) && item.decl.inputs.len() == 1 && is_unit(&item.decl.output);
- 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 span = item.span();
+
+ 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<Attribute>,
- pub unsafety: Option<Token![unsafe]>,
pub statics: BTreeMap<Ident, Static>,
+ pub context: Pat,
pub stmts: Vec<Stmt>,
}
@@ -832,49 +806,47 @@ pub type InterruptArgs = ExceptionArgs;
impl Interrupt {
fn check(args: InterruptArgs, item: ItemFn) -> parse::Result<Self> {
- 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<Attribute>,
pub attrs: Vec<Attribute>,
pub mutability: Option<Token![mut]>,
@@ -883,7 +855,7 @@ pub struct Resource {
}
impl Resource {
- fn check(mut item: ItemStatic) -> parse::Result<Resource> {
+ fn check(item: ItemStatic) -> parse::Result<Resource> {
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<Attribute>,
pub attrs: Vec<Attribute>,
- pub unsafety: Option<Token![unsafe]>,
pub inputs: Vec<ArgCaptured>,
+ pub context: Pat,
pub statics: BTreeMap<Ident, Static>,
pub stmts: Vec<Stmt>,
}
impl Task {
fn check(args: TaskArgs, item: ItemFn) -> parse::Result<Self> {
- 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`",
+ ));
+ }
+ _ => {}
+ }
- 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);
+ return Ok(Task {
+ args,
+ cfgs,
+ attrs,
+ inputs,
+ context,
+ statics: Static::parse(statics)?,
+ stmts,
+ });
}
- _ => {}
}
- 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<Stmt>) -> (Statics, Vec<Stmt>) {
(statics, stmts)
}
-// TODO remove in v0.5.x
-fn extract_assignments(stmts: Vec<Stmt>) -> (Vec<Stmt>, Vec<Assign>) {
- let mut istmts = stmts.into_iter().rev();
-
- 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;
- }
- }
+// 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<FnArg, Token![,]>,
+ name: &str,
+) -> Option<(Pat, Result<Vec<ArgCaptured>, FnArg>)> {
+ let mut inputs = inputs.into_iter();
+
+ 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::<Result<Vec<_>, _>>();
- 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()
+}
+
+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)
+ }
- (stmts.into_iter().rev().collect(), assigns)
+ _ => false,
+ }
}
fn is_bottom(ty: &ReturnType) -> bool {
diff --git a/src/export.rs b/src/export.rs
index cf7293b6..93a92fcf 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<N> = Queue<u8, N, usize, SingleCore>;
-pub type ReadyQueue<T, N> = Queue<(T, u8), N, usize, SingleCore>;
+pub type FreeQueue<N> = Queue<u8, N, u8, SingleCore>;
+pub type ReadyQueue<T, N> = Queue<(T, u8), N, u8, SingleCore>;
#[cfg(armv7m)]
#[inline(always)]
-pub fn run<F>(f: F)
+pub fn run<F>(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: F)
+pub fn run<F>(_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<T> {
- // we newtype so the end-user doesn't need `#![feature(maybe_uninit)]` in their code
inner: core::mem::MaybeUninit<T>,
}
-#[cfg(feature = "nightly")]
impl<T> MaybeUninit<T> {
pub const fn uninit() -> Self {
MaybeUninit {
@@ -86,61 +89,12 @@ impl<T> MaybeUninit<T> {
self.inner.as_mut_ptr()
}
- pub fn write(&mut self, value: T) -> &mut T {
- self.inner.write(value)
- }
-}
-
-#[cfg(not(feature = "nightly"))]
-pub struct MaybeUninit<T> {
- value: Option<T>,
-}
-
-#[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<T> MaybeUninit<T> {
- 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 unsafe fn read(&self) -> T {
+ self.inner.read()
}
- 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)) }
+ pub fn write(&mut self, value: T) -> &mut T {
+ self.inner.write(value)
}
}
@@ -160,19 +114,16 @@ where
#[cfg(armv7m)]
#[inline(always)]
-pub unsafe fn claim<T, R, F>(
+pub unsafe fn lock<T, R>(
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<T, R, F>(
+pub unsafe fn lock<T, R>(
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 b0bf6689..acd8d433 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<R, F>(&mut self, f: F) -> R
- where
- F: FnOnce(&mut Self::T) -> R;
+ fn lock<R>(&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<R, F>(&mut self, f: F) -> R
- where
- F: FnOnce(&mut Self::T) -> R,
- {
+ fn lock<R>(&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<R, F>(&mut self, f: F) -> R
- where
- F: FnOnce(&mut Self::T) -> R,
- {
+ fn lock<R>(&mut self, f: impl FnOnce(&mut T) -> R) -> R {
f(self.0)
}
}
diff --git a/src/tq.rs b/src/tq.rs
index 8d520518..8ca1bd3f 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<T, N>
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<T>
@@ -87,49 +115,3 @@ where
Some(self.cmp(&other))
}
}
-
-#[inline(always)]
-pub fn isr<TQ, T, N, F>(mut tq: TQ, mut f: F)
-where
- TQ: Mutex<T = TimerQueue<T, N>>,
- T: Copy + Send,
- N: ArrayLength<NotReady<T>>,
- 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;
- }
- }
-}