From 81275bfa4f41e2066770087f3a33cad4227eab41 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Thu, 13 Jun 2019 23:56:59 +0200 Subject: rtfm-syntax refactor + heterogeneous multi-core support --- macros/Cargo.toml | 12 +- macros/src/analyze.rs | 302 +--- macros/src/check.rs | 287 ++-- macros/src/codegen.rs | 2467 ++------------------------------ macros/src/codegen/assertions.rs | 26 + macros/src/codegen/dispatchers.rs | 178 +++ macros/src/codegen/hardware_tasks.rs | 121 ++ macros/src/codegen/idle.rs | 92 ++ macros/src/codegen/init.rs | 112 ++ macros/src/codegen/locals.rs | 94 ++ macros/src/codegen/module.rs | 328 +++++ macros/src/codegen/post_init.rs | 139 ++ macros/src/codegen/pre_init.rs | 150 ++ macros/src/codegen/resources.rs | 115 ++ macros/src/codegen/resources_struct.rs | 178 +++ macros/src/codegen/schedule.rs | 95 ++ macros/src/codegen/schedule_body.rs | 61 + macros/src/codegen/software_tasks.rs | 169 +++ macros/src/codegen/spawn.rs | 127 ++ macros/src/codegen/spawn_body.rs | 81 ++ macros/src/codegen/timer_queue.rs | 147 ++ macros/src/codegen/util.rs | 253 ++++ macros/src/lib.rs | 312 +--- macros/src/syntax.rs | 1382 ------------------ macros/src/tests.rs | 5 + macros/src/tests/multi.rs | 59 + macros/src/tests/single.rs | 35 + 27 files changed, 2916 insertions(+), 4411 deletions(-) create mode 100644 macros/src/codegen/assertions.rs create mode 100644 macros/src/codegen/dispatchers.rs create mode 100644 macros/src/codegen/hardware_tasks.rs create mode 100644 macros/src/codegen/idle.rs create mode 100644 macros/src/codegen/init.rs create mode 100644 macros/src/codegen/locals.rs create mode 100644 macros/src/codegen/module.rs create mode 100644 macros/src/codegen/post_init.rs create mode 100644 macros/src/codegen/pre_init.rs create mode 100644 macros/src/codegen/resources.rs create mode 100644 macros/src/codegen/resources_struct.rs create mode 100644 macros/src/codegen/schedule.rs create mode 100644 macros/src/codegen/schedule_body.rs create mode 100644 macros/src/codegen/software_tasks.rs create mode 100644 macros/src/codegen/spawn.rs create mode 100644 macros/src/codegen/spawn_body.rs create mode 100644 macros/src/codegen/timer_queue.rs create mode 100644 macros/src/codegen/util.rs delete mode 100644 macros/src/syntax.rs create mode 100644 macros/src/tests.rs create mode 100644 macros/src/tests/multi.rs create mode 100644 macros/src/tests/single.rs (limited to 'macros') diff --git a/macros/Cargo.toml b/macros/Cargo.toml index df20f8c..2854dad 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -15,12 +15,12 @@ version = "0.5.0-alpha.1" proc-macro = true [dependencies] -quote = "0.6.10" -proc-macro2 = "0.4.24" +proc-macro2 = "0.4.30" +quote = "0.6.12" +syn = "0.15.34" -[dependencies.syn] -features = ["extra-traits", "full"] -version = "0.15.23" +[dependencies.rtfm-syntax] +git = "https://github.com/japaric/rtfm-syntax" [features] -timer-queue = [] \ No newline at end of file +heterogeneous = [] diff --git a/macros/src/analyze.rs b/macros/src/analyze.rs index a47be77..e3ed778 100644 --- a/macros/src/analyze.rs +++ b/macros/src/analyze.rs @@ -1,265 +1,59 @@ -use std::{ - cmp, - collections::{BTreeMap, HashMap, HashSet}, -}; - -use syn::{Attribute, Ident, Type}; +use core::ops; +use std::collections::{BTreeMap, BTreeSet}; -use crate::syntax::{App, Idents}; - -pub type Ownerships = HashMap; +use rtfm_syntax::{ + analyze::{self, Priority}, + ast::App, + Core, P, +}; +use syn::Ident; +/// Extend the upstream `Analysis` struct with our field pub struct Analysis { - /// Capacities of free queues - pub capacities: Capacities, - pub dispatchers: Dispatchers, - // Ceilings of free queues - pub free_queues: HashMap, - pub resources_assert_send: HashSet>, - pub tasks_assert_send: HashSet, - /// Types of RO resources that need to be Sync - pub assert_sync: HashSet>, - // Resource ownership - pub ownerships: Ownerships, - // Ceilings of ready queues - pub ready_queues: HashMap, - pub timer_queue: TimerQueue, + parent: P, + pub interrupts: BTreeMap>, } -#[derive(Clone, Copy, PartialEq)] -pub enum Ownership { - // NOTE priorities and ceilings are "logical" (0 = lowest priority, 255 = highest priority) - Owned { priority: u8 }, - CoOwned { priority: u8 }, - Shared { ceiling: u8 }, -} - -impl Ownership { - pub fn needs_lock(&self, priority: u8) -> bool { - match *self { - Ownership::Owned { .. } | Ownership::CoOwned { .. } => false, - Ownership::Shared { ceiling } => { - debug_assert!(ceiling >= priority); - - priority < ceiling - } - } - } +impl ops::Deref for Analysis { + type Target = analyze::Analysis; - pub fn is_owned(&self) -> bool { - match *self { - Ownership::Owned { .. } => true, - _ => false, - } + fn deref(&self) -> &Self::Target { + &self.parent } } -pub struct Dispatcher { - /// Attributes to apply to the dispatcher - pub attrs: Vec, - pub interrupt: Ident, - /// Tasks dispatched at this priority level - pub tasks: Vec, - // Queue capacity - pub capacity: u8, -} - -/// Priority -> Dispatcher -pub type Dispatchers = BTreeMap; - -pub type Capacities = HashMap; - -pub fn app(app: &App) -> Analysis { - // Ceiling analysis of R/W resource and Sync analysis of RO resources - // (Resource shared by tasks that run at different priorities need to be `Sync`) - let mut ownerships = Ownerships::new(); - let mut resources_assert_send = HashSet::new(); - let mut tasks_assert_send = HashSet::new(); - let mut assert_sync = HashSet::new(); - - for (priority, res) in app.resource_accesses() { - if let Some(ownership) = ownerships.get_mut(res) { - match *ownership { - Ownership::Owned { priority: ceiling } - | Ownership::CoOwned { priority: ceiling } - | Ownership::Shared { ceiling } - if priority != ceiling => - { - *ownership = Ownership::Shared { - ceiling: cmp::max(ceiling, priority), - }; - - let res = &app.resources[res]; - if res.mutability.is_none() { - assert_sync.insert(res.ty.clone()); - } +// Assign an `extern` interrupt to each priority level +pub fn app(analysis: P, app: &App) -> P { + let mut interrupts = BTreeMap::new(); + for core in 0..app.args.cores { + let priorities = app + .software_tasks + .values() + .filter_map(|task| { + if task.args.core == core { + Some(task.args.priority) + } else { + None } - Ownership::Owned { priority: ceiling } if ceiling == priority => { - *ownership = Ownership::CoOwned { priority }; - } - _ => {} - } - - continue; - } - - ownerships.insert(res.clone(), Ownership::Owned { priority }); - } - - // Compute sizes of free queues - // We assume at most one message per `spawn` / `schedule` - let mut capacities: Capacities = app.tasks.keys().map(|task| (task.clone(), 0)).collect(); - for (_, task) in app.spawn_calls().chain(app.schedule_calls()) { - *capacities.get_mut(task).expect("BUG: capacities.get_mut") += 1; - } - - // Override computed capacities if user specified a capacity in `#[task]` - for (name, task) in &app.tasks { - if let Some(cap) = task.args.capacity { - *capacities.get_mut(name).expect("BUG: capacities.get_mut") = cap; - } - } - - // Compute the size of the timer queue - // Compute the priority of the timer queue, which matches the priority of the highest - // `schedule`-able task - let mut tq_capacity = 0; - let mut tq_priority = 1; - let mut tq_tasks = Idents::new(); - for (_, task) in app.schedule_calls() { - tq_capacity += capacities[task]; - tq_priority = cmp::max(tq_priority, app.tasks[task].args.priority); - tq_tasks.insert(task.clone()); - } - - // Compute dispatchers capacities - // Determine which tasks are dispatched by which dispatcher - // Compute the timer queue priority which matches the priority of the highest priority - // dispatcher - let mut dispatchers = Dispatchers::new(); - let mut free_interrupts = app.free_interrupts.iter(); - let mut tasks = app.tasks.iter().collect::>(); - tasks.sort_by(|l, r| l.1.args.priority.cmp(&r.1.args.priority)); - for (name, task) in tasks { - let dispatcher = dispatchers.entry(task.args.priority).or_insert_with(|| { - let (name, fi) = free_interrupts - .next() - .expect("BUG: not enough free_interrupts"); - - Dispatcher { - attrs: fi.attrs.clone(), - capacity: 0, - interrupt: name.clone(), - tasks: vec![], - } - }); - - dispatcher.capacity += capacities[name]; - dispatcher.tasks.push(name.clone()); - } - - // All messages sent from `init` need to be `Send` - for task in app.init.args.spawn.iter().chain(&app.init.args.schedule) { - tasks_assert_send.insert(task.clone()); - } - - // All late resources need to be `Send`, unless they are owned by `idle` - for (name, res) in &app.resources { - let owned_by_idle = Ownership::Owned { priority: 0 }; - if res.expr.is_none() - && ownerships - .get(name) - .map(|ship| *ship != owned_by_idle) - .unwrap_or(false) - { - resources_assert_send.insert(res.ty.clone()); - } - } - - // All resources shared with init need to be `Send`, unless they are owned by `idle` - // This is equivalent to late initialization (e.g. `static mut LATE: Option = None`) - for name in &app.init.args.resources { - let owned_by_idle = Ownership::Owned { priority: 0 }; - if ownerships - .get(name) - .map(|ship| *ship != owned_by_idle) - .unwrap_or(false) - { - resources_assert_send.insert(app.resources[name].ty.clone()); - } - } - - // Ceiling analysis of free queues (consumer end point) -- first pass - // Ceiling analysis of ready queues (producer end point) -- first pass - // Also compute more Send-ness requirements - 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 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 - .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 - // priority of the receiving task - if app.tasks[task].args.priority != priority { - tasks_assert_send.insert(task.clone()); - } - } else { - // spawns from `init` are excluded from the ceiling 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 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 queue - tq_ceiling = cmp::max(tq_ceiling, priority); - } else { - // spawns from `init` are excluded from the ceiling analysis - } - } - - Analysis { - capacities, - dispatchers, - free_queues, - tasks_assert_send, - resources_assert_send, - assert_sync, - ownerships, - ready_queues, - timer_queue: TimerQueue { - capacity: tq_capacity, - ceiling: tq_ceiling, - priority: tq_priority, - tasks: tq_tasks, - }, - } -} - -pub struct TimerQueue { - pub capacity: u8, - pub ceiling: u8, - pub priority: u8, - pub tasks: Idents, + }) + .chain(analysis.timer_queues.get(&core).map(|tq| tq.priority)) + .collect::>(); + + if !priorities.is_empty() { + interrupts.insert( + core, + priorities + .iter() + .cloned() + .rev() + .zip(app.extern_interrupts[&core].keys().cloned()) + .collect(), + ); + } + } + + P::new(Analysis { + parent: analysis, + interrupts, + }) } diff --git a/macros/src/check.rs b/macros/src/check.rs index 8ad13f3..c22a0f1 100644 --- a/macros/src/check.rs +++ b/macros/src/check.rs @@ -1,122 +1,209 @@ -use std::{collections::HashSet, iter}; +use std::collections::HashSet; use proc_macro2::Span; -use syn::parse; - -use crate::syntax::App; - -pub fn app(app: &App) -> parse::Result<()> { - // Check that all referenced resources have been declared - for res in app - .idle - .as_ref() - .map(|idle| -> Box> { Box::new(idle.args.resources.iter()) }) - .unwrap_or_else(|| Box::new(iter::empty())) - .chain(&app.init.args.resources) - .chain(app.exceptions.values().flat_map(|e| &e.args.resources)) - .chain(app.interrupts.values().flat_map(|i| &i.args.resources)) - .chain(app.tasks.values().flat_map(|t| &t.args.resources)) +use rtfm_syntax::{ + analyze::Analysis, + ast::{App, CustomArg, HardwareTaskKind}, +}; +use syn::{parse, Path}; + +pub struct Extra<'a> { + pub device: &'a Path, + pub monotonic: Option<&'a Path>, + pub peripherals: Option, +} + +impl<'a> Extra<'a> { + pub fn monotonic(&self) -> &'a Path { + self.monotonic.expect("UNREACHABLE") + } +} + +pub fn app<'a>(app: &'a App, analysis: &Analysis) -> parse::Result> { + // check that all exceptions are valid; only exceptions with configurable priorities are + // accepted + for (name, task) in app + .hardware_tasks + .iter() + .filter(|(_, task)| task.kind == HardwareTaskKind::Exception) { - if !app.resources.contains_key(res) { - return Err(parse::Error::new( - res.span(), - "this resource has NOT been declared", - )); + let name_s = task.args.binds(name).to_string(); + match &*name_s { + // NOTE that some of these don't exist on ARMv6-M but we don't check that here -- the + // code we generate will check that the exception actually exists on ARMv6-M + "MemoryManagement" | "BusFault" | "UsageFault" | "SecureFault" | "SVCall" + | "DebugMonitor" | "PendSV" => {} // OK + + "SysTick" => { + if analysis.timer_queues.get(&task.args.core).is_some() { + return Err(parse::Error::new( + name.span(), + "this exception can't be used because it's being used by the runtime", + )); + } else { + // OK + } + } + + _ => { + return Err(parse::Error::new( + name.span(), + "only exceptions with configurable priority can be used as hardware tasks", + )); + } } } - // Check that late resources have not been assigned to `init` - for res in &app.init.args.resources { - if app.resources.get(res).unwrap().expr.is_none() { - return Err(parse::Error::new( - res.span(), - "late resources can NOT be assigned to `init`", - )); + // check that external (device-specific) interrupts are not named after known (Cortex-M) + // exceptions + for name in app + .extern_interrupts + .iter() + .flat_map(|(_, interrupts)| interrupts.keys()) + { + let name_s = name.to_string(); + + match &*name_s { + "NonMaskableInt" | "HardFault" | "MemoryManagement" | "BusFault" | "UsageFault" + | "SecureFault" | "SVCall" | "DebugMonitor" | "PendSV" | "SysTick" => { + return Err(parse::Error::new( + name.span(), + "Cortex-M exceptions can't be used as `extern` interrupts", + )); + } + + _ => {} } } - if app.resources.iter().any(|(_, res)| res.expr.is_none()) { - // Check that `init` returns `LateResources` if there's any declared late resource - if !app.init.returns_late_resources { - return Err(parse::Error::new( - app.init.span, - "late resources have been specified so `init` must return `init::LateResources`", - )); - } - } else if app.init.returns_late_resources { - // If there are no late resources the signature should be `fn(init::Context)` - if app.init.returns_late_resources { - return Err(parse::Error::new( - app.init.span, - "`init` signature must be `fn(init::Context)` if there are no late resources", - )); + // check that there are enough external interrupts to dispatch the software tasks and the timer + // queue handler + for core in 0..app.args.cores { + let mut first = None; + let priorities = app + .software_tasks + .iter() + .filter_map(|(name, task)| { + if task.args.core == core { + first = Some(name); + Some(task.args.priority) + } else { + None + } + }) + .chain(analysis.timer_queues.get(&core).map(|tq| tq.priority)) + .collect::>(); + + let need = priorities.len(); + let given = app + .extern_interrupts + .get(&core) + .map(|ei| ei.len()) + .unwrap_or(0); + if need > given { + let s = if app.args.cores == 1 { + format!( + "not enough `extern` interrupts to dispatch \ + all software tasks (need: {}; given: {})", + need, given + ) + } else { + format!( + "not enough `extern` interrupts to dispatch \ + all software tasks on this core (need: {}; given: {})", + need, given + ) + }; + + return Err(parse::Error::new(first.unwrap().span(), &s)); } } - // Check that all referenced tasks have been declared - for task in app - .idle - .as_ref() - .map(|idle| -> Box> { - Box::new(idle.args.schedule.iter().chain(&idle.args.spawn)) - }) - .unwrap_or_else(|| Box::new(iter::empty())) - .chain(&app.init.args.schedule) - .chain(&app.init.args.spawn) - .chain( - app.exceptions - .values() - .flat_map(|e| e.args.schedule.iter().chain(&e.args.spawn)), - ) - .chain( - app.interrupts - .values() - .flat_map(|i| i.args.schedule.iter().chain(&i.args.spawn)), - ) - .chain( - app.tasks - .values() - .flat_map(|t| t.args.schedule.iter().chain(&t.args.spawn)), - ) - { - if !app.tasks.contains_key(task) { - return Err(parse::Error::new( - task.span(), - "this task has NOT been declared", - )); + let mut device = None; + let mut monotonic = None; + let mut peripherals = None; + + for (k, v) in &app.args.custom { + let ks = k.to_string(); + + match &*ks { + "device" => match v { + CustomArg::Path(p) => device = Some(p), + + _ => { + return Err(parse::Error::new( + k.span(), + "unexpected argument value; this should be a path", + )); + } + }, + + "monotonic" => match v { + CustomArg::Path(p) => monotonic = Some(p), + + _ => { + return Err(parse::Error::new( + k.span(), + "unexpected argument value; this should be a path", + )); + } + }, + + "peripherals" => match v { + CustomArg::Bool(x) if app.args.cores == 1 => { + peripherals = if *x { Some(0) } else { None } + } + + CustomArg::UInt(x) if app.args.cores != 1 => { + peripherals = if *x < u64::from(app.args.cores) { + Some(*x as u8) + } else { + return Err(parse::Error::new( + k.span(), + &format!( + "unexpected argument value; \ + this should be an integer in the range 0..={}", + app.args.cores + ), + )); + } + } + + _ => { + return Err(parse::Error::new( + k.span(), + if app.args.cores == 1 { + "unexpected argument value; this should be a boolean" + } else { + "unexpected argument value; this should be an integer" + }, + )); + } + }, + + _ => { + return Err(parse::Error::new(k.span(), "unexpected argument")); + } } } - // Check that there are enough free interrupts to dispatch all tasks - let ndispatchers = app - .tasks - .values() - .map(|t| t.args.priority) - .collect::>() - .len(); - if ndispatchers > app.free_interrupts.len() { + if !analysis.timer_queues.is_empty() && monotonic.is_none() { return Err(parse::Error::new( Span::call_site(), - &*format!( - "{} free interrupt{} (`extern {{ .. }}`) {} required to dispatch all soft tasks", - ndispatchers, - if ndispatchers > 1 { "s" } else { "" }, - if ndispatchers > 1 { "are" } else { "is" }, - ), + "a `monotonic` timer must be specified to use the `schedule` API", )); } - // Check that free interrupts are not being used - for (handler, interrupt) in &app.interrupts { - let name = interrupt.args.binds(handler); - - if app.free_interrupts.contains_key(name) { - return Err(parse::Error::new( - name.span(), - "free interrupts (`extern { .. }`) can't be used as interrupt handlers", - )); - } + if let Some(device) = device { + Ok(Extra { + device, + monotonic, + peripherals, + }) + } else { + Err(parse::Error::new( + Span::call_site(), + "a `device` argument must be specified in `#[rtfm::app]`", + )) } - - Ok(()) } diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs index 88f1173..86b4a67 100644 --- a/macros/src/codegen.rs +++ b/macros/src/codegen.rs @@ -1,136 +1,75 @@ -use proc_macro::TokenStream; -use std::collections::{BTreeMap, BTreeSet}; - -use proc_macro2::Span; +use proc_macro2::TokenStream as TokenStream2; use quote::quote; -use syn::{ArgCaptured, Attribute, Ident, IntSuffix, LitInt}; - -use crate::{ - analyze::{Analysis, Ownership}, - syntax::{App, Static}, -}; - -pub fn app(name: &Ident, app: &App, analysis: &Analysis) -> TokenStream { - let (const_app_resources, mod_resources) = resources(app, analysis); - - let ( - const_app_exceptions, - exception_mods, - exception_locals, - exception_resources, - user_exceptions, - ) = exceptions(app, analysis); - - let ( - const_app_interrupts, - interrupt_mods, - interrupt_locals, - interrupt_resources, - user_interrupts, - ) = interrupts(app, analysis); - - let (const_app_tasks, task_mods, task_locals, task_resources, user_tasks) = - tasks(app, analysis); - - let const_app_dispatchers = dispatchers(&app, analysis); - - let const_app_spawn = spawn(app, analysis); - - let const_app_tq = timer_queue(app, analysis); - - let const_app_schedule = schedule(app); - - let assertion_stmts = assertions(app, analysis); - - let pre_init_stmts = pre_init(&app, analysis); - - let ( - const_app_init, - mod_init, - init_locals, - init_resources, - init_late_resources, - user_init, - call_init, - ) = init(app, analysis); - - let post_init_stmts = post_init(&app, analysis); - - let (const_app_idle, mod_idle, idle_locals, idle_resources, user_idle, call_idle) = - idle(app, analysis); - - let device = &app.args.device; - quote!( - #user_init - - #user_idle - - #(#user_exceptions)* - - #(#user_interrupts)* - - #(#user_tasks)* - - #mod_resources - - #init_locals - - #init_resources - - #init_late_resources - - #mod_init - - #idle_locals - - #idle_resources - - #mod_idle - - #(#exception_locals)* +use rtfm_syntax::ast::App; + +use crate::{analyze::Analysis, check::Extra}; + +mod assertions; +mod dispatchers; +mod hardware_tasks; +mod idle; +mod init; +mod locals; +mod module; +mod post_init; +mod pre_init; +mod resources; +mod resources_struct; +mod schedule; +mod schedule_body; +mod software_tasks; +mod spawn; +mod spawn_body; +mod timer_queue; +mod util; + +// TODO document the syntax here or in `rtfm-syntax` +pub fn app(app: &App, analysis: &Analysis, extra: &Extra) -> TokenStream2 { + let mut const_app = vec![]; + let mut mains = vec![]; + let mut root = vec![]; + let mut user = vec![]; - #(#exception_resources)* + // generate a `main` function for each core + for core in 0..app.args.cores { + let assertion_stmts = assertions::codegen(core, analysis); - #(#exception_mods)* + let (const_app_pre_init, pre_init_stmts) = pre_init::codegen(core, &app, analysis, extra); - #(#interrupt_locals)* + let (const_app_init, root_init, user_init, call_init) = + init::codegen(core, app, analysis, extra); - #(#interrupt_resources)* + let (const_app_post_init, post_init_stmts) = post_init::codegen(core, analysis, extra); - #(#interrupt_mods)* + let (const_app_idle, root_idle, user_idle, call_idle) = + idle::codegen(core, app, analysis, extra); - #(#task_locals)* + user.push(quote!( + #user_init - #(#task_resources)* + #user_idle + )); - #(#task_mods)* + root.push(quote!( + #(#root_init)* - /// Implementation details - const #name: () = { - // always include the device crate, which contains the vector table - use #device as _; + #(#root_idle)* + )); - #(#const_app_resources)* + const_app.push(quote!( + #(#const_app_pre_init)* #const_app_init - #const_app_idle - - #(#const_app_exceptions)* - - #(#const_app_interrupts)* - - #(#const_app_dispatchers)* - - #(#const_app_tasks)* - - #(#const_app_spawn)* - - #(#const_app_tq)* + #(#const_app_post_init)* - #(#const_app_schedule)* + #const_app_idle + )); + let cfg_core = util::cfg_core(core, app.args.cores); + mains.push(quote!( #[no_mangle] + #cfg_core unsafe fn main() -> ! { #(#assertion_stmts)* @@ -142,2297 +81,65 @@ pub fn app(name: &Ident, app: &App, analysis: &Analysis) -> TokenStream { #call_idle } - }; - ) - .into() -} - -/* Main functions */ -/// In this pass we generate a static variable and a resource proxy for each resource -/// -/// If the user specified a resource like this: -/// -/// ``` -/// #[rtfm::app(device = ..)] -/// const APP: () = { -/// static mut X: UserDefinedStruct = (); -/// static mut Y: u64 = 0; -/// static mut Z: u32 = 0; -/// } -/// ``` -/// -/// We'll generate code like this: -/// -/// - `const_app` -/// -/// ``` -/// const APP: () = { -/// static mut X: MaybeUninit = MaybeUninit::uninit(); -/// static mut Y: u64 = 0; -/// static mut Z: u32 = 0; -/// -/// impl<'a> Mutex for resources::X<'a> { .. } -/// -/// impl<'a> Mutex for resources::Y<'a> { .. } -/// -/// // but not for `Z` because it's not shared and thus requires no proxy -/// }; -/// ``` -/// -/// - `mod_resources` -/// -/// ``` -/// mod resources { -/// pub struct X<'a> { -/// priority: &'a Priority, -/// } -/// -/// impl<'a> X<'a> { -/// pub unsafe fn new(priority: &'a Priority) -> Self { -/// X { priority } -/// } -/// -/// pub unsafe fn priority(&self) -> &Priority { -/// self.priority -/// } -/// } -/// -/// // same thing for `Y` -/// -/// // but not for `Z` -/// } -/// ``` -fn resources( - app: &App, - analysis: &Analysis, -) -> ( - // const_app - Vec, - // mod_resources - proc_macro2::TokenStream, -) { - let mut const_app = vec![]; - let mut mod_resources = vec![]; - - for (name, res) in &app.resources { - let cfgs = &res.cfgs; - let attrs = &res.attrs; - let ty = &res.ty; - - if let Some(expr) = res.expr.as_ref() { - const_app.push(quote!( - #(#attrs)* - #(#cfgs)* - static mut #name: #ty = #expr; - )); - } else { - const_app.push(quote!( - #(#attrs)* - #(#cfgs)* - static mut #name: core::mem::MaybeUninit<#ty> = - core::mem::MaybeUninit::uninit(); - )); - } - - // generate a resource proxy when needed - if res.mutability.is_some() { - if let Some(Ownership::Shared { ceiling }) = analysis.ownerships.get(name) { - let ptr = if res.expr.is_none() { - quote!(#name.as_mut_ptr()) - } else { - quote!(&mut #name) - }; - - mod_resources.push(quote!( - pub struct #name<'a> { - priority: &'a Priority, - } - - impl<'a> #name<'a> { - #[inline(always)] - pub unsafe fn new(priority: &'a Priority) -> Self { - #name { priority } - } - - #[inline(always)] - pub unsafe fn priority(&self) -> &Priority { - self.priority - } - } - )); - - const_app.push(impl_mutex( - app, - cfgs, - true, - name, - quote!(#ty), - *ceiling, - ptr, - )); - } - } - } - - let mod_resources = if mod_resources.is_empty() { - quote!() - } else { - quote!(mod resources { - use rtfm::export::Priority; - - #(#mod_resources)* - }) - }; - - (const_app, mod_resources) -} - -// For each exception we'll generate: -// -// - at the root of the crate: -// - a ${name}Resources struct (maybe) -// - a ${name}Locals struct -// -// - a module named after the exception, see the `module` function for more details -// -// - hidden in `const APP` -// - the ${name}Resources constructor -// -// - the exception handler specified by the user -fn exceptions( - app: &App, - analysis: &Analysis, -) -> ( - // const_app - Vec, - // exception_mods - Vec, - // exception_locals - Vec, - // exception_resources - Vec, - // user_exceptions - Vec, -) { - let mut const_app = vec![]; - let mut mods = vec![]; - let mut locals_structs = vec![]; - let mut resources_structs = vec![]; - let mut user_code = vec![]; - - for (name, exception) in &app.exceptions { - let (let_instant, instant) = if cfg!(feature = "timer-queue") { - ( - Some(quote!(let instant = rtfm::Instant::now();)), - Some(quote!(, instant)), - ) - } else { - (None, None) - }; - let priority = &exception.args.priority; - let symbol = exception.args.binds(name); - const_app.push(quote!( - #[allow(non_snake_case)] - #[no_mangle] - unsafe fn #symbol() { - const PRIORITY: u8 = #priority; - - #let_instant - - rtfm::export::run(PRIORITY, || { - crate::#name( - #name::Locals::new(), - #name::Context::new(&rtfm::export::Priority::new(PRIORITY) #instant) - ) - }); - } - )); - - let mut needs_lt = false; - if !exception.args.resources.is_empty() { - let (item, constructor) = resources_struct( - Kind::Exception(name.clone()), - exception.args.priority, - &mut needs_lt, - app, - analysis, - ); - - resources_structs.push(item); - - const_app.push(constructor); - } - - mods.push(module( - Kind::Exception(name.clone()), - (!exception.args.resources.is_empty(), needs_lt), - !exception.args.schedule.is_empty(), - !exception.args.spawn.is_empty(), - false, - app, - )); - - let attrs = &exception.attrs; - let context = &exception.context; - let (locals, lets) = locals(Kind::Exception(name.clone()), &exception.statics); - locals_structs.push(locals); - let use_u32ext = if cfg!(feature = "timer-queue") { - Some(quote!( - use rtfm::U32Ext as _; - )) - } else { - None - }; - let stmts = &exception.stmts; - user_code.push(quote!( - #(#attrs)* - #[allow(non_snake_case)] - fn #name(__locals: #name::Locals, #context: #name::Context) { - #use_u32ext - use rtfm::Mutex as _; - - #(#lets;)* - - #(#stmts)* - } - )); - } - - ( - const_app, - mods, - locals_structs, - resources_structs, - user_code, - ) -} - -// For each interrupt we'll generate: -// -// - at the root of the crate: -// - a ${name}Resources struct (maybe) -// - a ${name}Locals struct -// -// - a module named after the exception, see the `module` function for more details -// -// - hidden in `const APP` -// - the ${name}Resources constructor -// -// - the interrupt handler specified by the user -fn interrupts( - app: &App, - analysis: &Analysis, -) -> ( - // const_app - Vec, - // interrupt_mods - Vec, - // interrupt_locals - Vec, - // interrupt_resources - Vec, - // user_exceptions - Vec, -) { - let mut const_app = vec![]; - let mut mods = vec![]; - let mut locals_structs = vec![]; - let mut resources_structs = vec![]; - let mut user_code = vec![]; - - let device = &app.args.device; - for (name, interrupt) in &app.interrupts { - let (let_instant, instant) = if cfg!(feature = "timer-queue") { - ( - Some(quote!(let instant = rtfm::Instant::now();)), - Some(quote!(, instant)), - ) - } else { - (None, None) - }; - let priority = &interrupt.args.priority; - let symbol = interrupt.args.binds(name); - const_app.push(quote!( - #[allow(non_snake_case)] - #[no_mangle] - unsafe fn #symbol() { - const PRIORITY: u8 = #priority; - - #let_instant - - // check that this interrupt exists - let _ = #device::Interrupt::#symbol; - - rtfm::export::run(PRIORITY, || { - crate::#name( - #name::Locals::new(), - #name::Context::new(&rtfm::export::Priority::new(PRIORITY) #instant) - ) - }); - } - )); - - let mut needs_lt = false; - if !interrupt.args.resources.is_empty() { - let (item, constructor) = resources_struct( - Kind::Interrupt(name.clone()), - interrupt.args.priority, - &mut needs_lt, - app, - analysis, - ); - - resources_structs.push(item); - - const_app.push(constructor); - } - - mods.push(module( - Kind::Interrupt(name.clone()), - (!interrupt.args.resources.is_empty(), needs_lt), - !interrupt.args.schedule.is_empty(), - !interrupt.args.spawn.is_empty(), - false, - app, - )); - - let attrs = &interrupt.attrs; - let context = &interrupt.context; - let use_u32ext = if cfg!(feature = "timer-queue") { - Some(quote!( - use rtfm::U32Ext as _; - )) - } else { - None - }; - let (locals, lets) = locals(Kind::Interrupt(name.clone()), &interrupt.statics); - locals_structs.push(locals); - let stmts = &interrupt.stmts; - user_code.push(quote!( - #(#attrs)* - #[allow(non_snake_case)] - fn #name(__locals: #name::Locals, #context: #name::Context) { - #use_u32ext - use rtfm::Mutex as _; - - #(#lets;)* - - #(#stmts)* - } - )); - } - - ( - const_app, - mods, - locals_structs, - resources_structs, - user_code, - ) -} - -// For each task we'll generate: -// -// - at the root of the crate: -// - a ${name}Resources struct (maybe) -// - a ${name}Locals struct -// -// - a module named after the task, see the `module` function for more details -// -// - hidden in `const APP` -// - the ${name}Resources constructor -// - an INPUTS buffer -// - a free queue and a corresponding resource -// - an INSTANTS buffer (if `timer-queue` is enabled) -// -// - the task handler specified by the user -fn tasks( - app: &App, - analysis: &Analysis, -) -> ( - // const_app - Vec, - // task_mods - Vec, - // task_locals - Vec, - // task_resources - Vec, - // user_tasks - Vec, -) { - let mut const_app = vec![]; - let mut mods = vec![]; - let mut locals_structs = vec![]; - let mut resources_structs = vec![]; - let mut user_code = vec![]; - - for (name, task) in &app.tasks { - let inputs = &task.inputs; - let (_, _, _, ty) = regroup_inputs(inputs); - - let cap = analysis.capacities[name]; - let cap_lit = mk_capacity_literal(cap); - let cap_ty = mk_typenum_capacity(cap, true); - - let task_inputs = mk_inputs_ident(name); - let task_instants = mk_instants_ident(name); - let task_fq = mk_fq_ident(name); - - let elems = (0..cap) - .map(|_| quote!(core::mem::MaybeUninit::uninit())) - .collect::>(); - - if cfg!(feature = "timer-queue") { - let elems = elems.clone(); - const_app.push(quote!( - /// Buffer that holds the instants associated to the inputs of a task - static mut #task_instants: [core::mem::MaybeUninit; #cap_lit] = - [#(#elems,)*]; - )); - } - - const_app.push(quote!( - /// Buffer that holds the inputs of a task - static mut #task_inputs: [core::mem::MaybeUninit<#ty>; #cap_lit] = - [#(#elems,)*]; - )); - - let doc = "Queue version of a free-list that keeps track of empty slots in the previous buffer(s)"; - let fq_ty = quote!(rtfm::export::FreeQueue<#cap_ty>); - const_app.push(quote!( - #[doc = #doc] - static mut #task_fq: #fq_ty = unsafe { - rtfm::export::Queue(rtfm::export::i::Queue::u8_sc()) - }; - )); - let ptr = quote!(&mut #task_fq); - - if let Some(ceiling) = analysis.free_queues.get(name) { - const_app.push(quote!(struct #task_fq<'a> { - priority: &'a rtfm::export::Priority, - })); - - const_app.push(impl_mutex(app, &[], false, &task_fq, fq_ty, *ceiling, ptr)); - } - - let mut needs_lt = false; - if !task.args.resources.is_empty() { - let (item, constructor) = resources_struct( - Kind::Task(name.clone()), - task.args.priority, - &mut needs_lt, - app, - analysis, - ); - - resources_structs.push(item); - - const_app.push(constructor); - } - - mods.push(module( - Kind::Task(name.clone()), - (!task.args.resources.is_empty(), needs_lt), - !task.args.schedule.is_empty(), - !task.args.spawn.is_empty(), - false, - app, - )); - - let attrs = &task.attrs; - let use_u32ext = if cfg!(feature = "timer-queue") { - Some(quote!( - use rtfm::U32Ext as _; - )) - } else { - None - }; - let context = &task.context; - let stmts = &task.stmts; - let (locals_struct, lets) = locals(Kind::Task(name.clone()), &task.statics); - locals_structs.push(locals_struct); - user_code.push(quote!( - #(#attrs)* - #[allow(non_snake_case)] - fn #name(__locals: #name::Locals, #context: #name::Context #(,#inputs)*) { - use rtfm::Mutex as _; - #use_u32ext - - #(#lets;)* - - #(#stmts)* - } - )); - } - - ( - const_app, - mods, - locals_structs, - resources_structs, - user_code, - ) -} - -/// For each task dispatcher we'll generate -/// -/// - A static variable that hold the ready queue (`RQ${priority}`) and a resource proxy for it -/// - An enumeration of all the tasks dispatched by this dispatcher `T${priority}` -/// - An interrupt handler that dispatches the tasks -fn dispatchers(app: &App, analysis: &Analysis) -> Vec { - let mut items = vec![]; - - let device = &app.args.device; - for (level, dispatcher) in &analysis.dispatchers { - let rq = mk_rq_ident(*level); - let t = mk_t_ident(*level); - let cap = mk_typenum_capacity(dispatcher.capacity, true); - - let doc = format!( - "Queue of tasks ready to be dispatched at priority level {}", - level - ); - let rq_ty = quote!(rtfm::export::ReadyQueue<#t, #cap>); - items.push(quote!( - #[doc = #doc] - static mut #rq: #rq_ty = unsafe { - rtfm::export::Queue(rtfm::export::i::Queue::u8_sc()) - }; - )); - let ptr = quote!(&mut #rq); - - if let Some(ceiling) = analysis.ready_queues.get(&level) { - items.push(quote!( - struct #rq<'a> { - priority: &'a rtfm::export::Priority, - } - )); - - items.push(impl_mutex(app, &[], false, &rq, rq_ty, *ceiling, ptr)); - } - - let variants = dispatcher - .tasks - .iter() - .map(|task| { - let cfgs = &app.tasks[task].cfgs; - - quote!( - #(#cfgs)* - #task - ) - }) - .collect::>(); - - let doc = format!( - "Software tasks to be dispatched at priority level {}", - level - ); - items.push(quote!( - #[allow(non_camel_case_types)] - #[derive(Clone, Copy)] - #[doc = #doc] - enum #t { - #(#variants,)* - } - )); - - let arms = dispatcher - .tasks - .iter() - .map(|name| { - let task = &app.tasks[name]; - let cfgs = &task.cfgs; - let (_, tupled, pats, _) = regroup_inputs(&task.inputs); - - let inputs = mk_inputs_ident(name); - let fq = mk_fq_ident(name); - - let input = quote!(#inputs.get_unchecked(usize::from(index)).as_ptr().read()); - let fq = quote!(#fq); - - let (let_instant, _instant) = if cfg!(feature = "timer-queue") { - let instants = mk_instants_ident(name); - let instant = - quote!(#instants.get_unchecked(usize::from(index)).as_ptr().read()); - - ( - Some(quote!(let instant = #instant;)), - Some(quote!(, instant)), - ) - } else { - (None, None) - }; - - let call = { - let pats = pats.clone(); - - quote!( - #name( - #name::Locals::new(), - #name::Context::new(priority #_instant) - #(,#pats)* - ) - ) - }; - - quote!( - #(#cfgs)* - #t::#name => { - let #tupled = #input; - #let_instant - #fq.split().0.enqueue_unchecked(index); - let priority = &rtfm::export::Priority::new(PRIORITY); - #call - } - ) - }) - .collect::>(); - - let doc = format!( - "interrupt handler used to dispatch tasks at priority {}", - level - ); - let attrs = &dispatcher.attrs; - let interrupt = &dispatcher.interrupt; - let rq = quote!((&mut #rq)); - items.push(quote!( - #[doc = #doc] - #(#attrs)* - #[no_mangle] - #[allow(non_snake_case)] - unsafe fn #interrupt() { - /// The priority of this interrupt handler - const PRIORITY: u8 = #level; - - // check that this interrupt exists - let _ = #device::Interrupt::#interrupt; - - rtfm::export::run(PRIORITY, || { - while let Some((task, index)) = #rq.split().1.dequeue() { - match task { - #(#arms)* - } - } - }); - } - )); - } - - items -} - -/// Generates all the `Spawn.$task` related code -fn spawn(app: &App, analysis: &Analysis) -> Vec { - let mut items = vec![]; - - let mut seen = BTreeSet::new(); - for (spawner, spawnees) in app.spawn_callers() { - if spawnees.is_empty() { - continue; - } - - let mut methods = vec![]; - - let spawner_is_init = spawner == "init"; - let spawner_is_idle = spawner == "idle"; - for name in spawnees { - let spawnee = &app.tasks[name]; - let cfgs = &spawnee.cfgs; - let (args, _, untupled, ty) = regroup_inputs(&spawnee.inputs); - - if spawner_is_init { - // `init` uses a special spawn implementation; it doesn't use the `spawn_${name}` - // functions which are shared by other contexts - - let body = mk_spawn_body(&spawner, &name, app, analysis); - - let let_instant = if cfg!(feature = "timer-queue") { - Some(quote!(let instant = unsafe { rtfm::Instant::artificial(0) };)) - } else { - None - }; - methods.push(quote!( - #(#cfgs)* - fn #name(&self #(,#args)*) -> Result<(), #ty> { - #let_instant - #body - } - )); - } else { - let spawn = mk_spawn_ident(name); - - if !seen.contains(name) { - // generate a `spawn_${name}` function - seen.insert(name); - - let instant = if cfg!(feature = "timer-queue") { - Some(quote!(, instant: rtfm::Instant)) - } else { - None - }; - let body = mk_spawn_body(&spawner, &name, app, analysis); - let args = args.clone(); - items.push(quote!( - #(#cfgs)* - unsafe fn #spawn( - priority: &rtfm::export::Priority - #instant - #(,#args)* - ) -> Result<(), #ty> { - #body - } - )); - } - - let (let_instant, instant) = if cfg!(feature = "timer-queue") { - ( - Some(if spawner_is_idle { - quote!(let instant = rtfm::Instant::now();) - } else { - quote!(let instant = self.instant();) - }), - Some(quote!(, instant)), - ) - } else { - (None, None) - }; - methods.push(quote!( - #(#cfgs)* - #[inline(always)] - fn #name(&self #(,#args)*) -> Result<(), #ty> { - unsafe { - #let_instant - #spawn(self.priority() #instant #(,#untupled)*) - } - } - )); - } - } - - let lt = if spawner_is_init { - None - } else { - Some(quote!('a)) - }; - items.push(quote!( - impl<#lt> #spawner::Spawn<#lt> { - #(#methods)* - } - )); - } - - items -} - -/// Generates code related to the timer queue, namely -/// -/// - A static variable that holds the timer queue and a resource proxy for it -/// - The system timer exception, which moves tasks from the timer queue into the ready queues -fn timer_queue(app: &App, analysis: &Analysis) -> Vec { - let mut items = vec![]; - - let tasks = &analysis.timer_queue.tasks; - - if tasks.is_empty() { - return items; - } - - let variants = tasks - .iter() - .map(|task| { - let cfgs = &app.tasks[task].cfgs; - quote!( - #(#cfgs)* - #task - ) - }) - .collect::>(); - - items.push(quote!( - /// `schedule`-dable tasks - #[allow(non_camel_case_types)] - #[derive(Clone, Copy)] - enum T { - #(#variants,)* - } - )); - - let cap = mk_typenum_capacity(analysis.timer_queue.capacity, false); - let ty = quote!(rtfm::export::TimerQueue); - items.push(quote!( - /// The timer queue - static mut TQ: core::mem::MaybeUninit<#ty> = core::mem::MaybeUninit::uninit(); - )); - - items.push(quote!( - struct TQ<'a> { - priority: &'a rtfm::export::Priority, - } - )); - - items.push(impl_mutex( - app, - &[], - false, - &Ident::new("TQ", Span::call_site()), - ty, - analysis.timer_queue.ceiling, - quote!(TQ.as_mut_ptr()), - )); - - let device = &app.args.device; - let arms = tasks - .iter() - .map(|name| { - let task = &app.tasks[name]; - let cfgs = &task.cfgs; - let priority = task.args.priority; - let rq = mk_rq_ident(priority); - let t = mk_t_ident(priority); - let dispatcher = &analysis.dispatchers[&priority].interrupt; - - quote!( - #(#cfgs)* - T::#name => { - let priority = &rtfm::export::Priority::new(PRIORITY); - (#rq { priority }).lock(|rq| { - rq.split().0.enqueue_unchecked((#t::#name, index)) - }); - - rtfm::pend(#device::Interrupt::#dispatcher) - } - ) - }) - .collect::>(); - - let priority = analysis.timer_queue.priority; - items.push(quote!( - /// The system timer - #[no_mangle] - unsafe fn SysTick() { - use rtfm::Mutex as _; - - /// System timer priority - const PRIORITY: u8 = #priority; - - rtfm::export::run(PRIORITY, || { - while let Some((task, index)) = (TQ { - // NOTE dynamic priority is always the static priority at this point - priority: &rtfm::export::Priority::new(PRIORITY), - }) - // NOTE `inline(always)` produces faster and smaller code - .lock(#[inline(always)] - |tq| tq.dequeue()) - { - match task { - #(#arms)* - } - } - }); - } - )); - - items -} - -/// Generates all the `Schedule.$task` related code -fn schedule(app: &App) -> Vec { - let mut items = vec![]; - if !cfg!(feature = "timer-queue") { - return items; - } - - let mut seen = BTreeSet::new(); - for (scheduler, schedulees) in app.schedule_callers() { - if schedulees.is_empty() { - continue; - } - - let mut methods = vec![]; - - let scheduler_is_init = scheduler == "init"; - for name in schedulees { - let schedulee = &app.tasks[name]; - - let (args, _, untupled, ty) = regroup_inputs(&schedulee.inputs); - - let cfgs = &schedulee.cfgs; - - let schedule = mk_schedule_ident(name); - if scheduler_is_init { - let body = mk_schedule_body(&scheduler, name, app); - - let args = args.clone(); - methods.push(quote!( - #(#cfgs)* - fn #name(&self, instant: rtfm::Instant #(,#args)*) -> Result<(), #ty> { - #body - } - )); - } else { - if !seen.contains(name) { - seen.insert(name); - - let body = mk_schedule_body(&scheduler, name, app); - let args = args.clone(); - - items.push(quote!( - #(#cfgs)* - fn #schedule( - priority: &rtfm::export::Priority, - instant: rtfm::Instant - #(,#args)* - ) -> Result<(), #ty> { - #body - } - )); - } - - methods.push(quote!( - #(#cfgs)* - #[inline(always)] - fn #name(&self, instant: rtfm::Instant #(,#args)*) -> Result<(), #ty> { - let priority = unsafe { self.priority() }; - - #schedule(priority, instant #(,#untupled)*) - } - )); - } - } - - let lt = if scheduler_is_init { - None - } else { - Some(quote!('a)) - }; - items.push(quote!( - impl<#lt> #scheduler::Schedule<#lt> { - #(#methods)* - } - )); - } - - items -} - -/// Generates `Send` / `Sync` compile time checks -fn assertions(app: &App, analysis: &Analysis) -> Vec { - let mut stmts = vec![]; - - for ty in &analysis.assert_sync { - stmts.push(quote!(rtfm::export::assert_sync::<#ty>();)); - } - - for task in &analysis.tasks_assert_send { - let (_, _, _, ty) = regroup_inputs(&app.tasks[task].inputs); - stmts.push(quote!(rtfm::export::assert_send::<#ty>();)); - } - - // all late resources need to be `Send` - for ty in &analysis.resources_assert_send { - stmts.push(quote!(rtfm::export::assert_send::<#ty>();)); - } - - stmts -} - -/// Generates code that we must run before `init` runs. See comments inside -fn pre_init(app: &App, analysis: &Analysis) -> Vec { - let mut stmts = vec![]; - - stmts.push(quote!(rtfm::export::interrupt::disable();)); - - // populate the `FreeQueue`s - for name in app.tasks.keys() { - let fq = mk_fq_ident(name); - let cap = analysis.capacities[name]; - - stmts.push(quote!( - for i in 0..#cap { - #fq.enqueue_unchecked(i); - } - )); - } - - stmts.push(quote!( - let mut core = rtfm::export::Peripherals::steal(); - )); - - // Initialize the timer queue - if !analysis.timer_queue.tasks.is_empty() { - stmts.push(quote!(TQ.as_mut_ptr().write(rtfm::export::TimerQueue::new(core.SYST));)); - } - - // set interrupts priorities - let device = &app.args.device; - let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS); - for (handler, interrupt) in &app.interrupts { - let name = interrupt.args.binds(handler); - let priority = interrupt.args.priority; - - stmts.push(quote!(core.NVIC.enable(#device::Interrupt::#name);)); - - // compile time assert that this priority is supported by the device - stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];)); - - stmts.push(quote!( - core.NVIC.set_priority( - #device::Interrupt::#name, - rtfm::export::logical2hw(#priority, #nvic_prio_bits), - ); )); } - // set task dispatcher priorities - for (priority, dispatcher) in &analysis.dispatchers { - let name = &dispatcher.interrupt; - - stmts.push(quote!(core.NVIC.enable(#device::Interrupt::#name);)); - - // compile time assert that this priority is supported by the device - stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];)); - - stmts.push(quote!( - core.NVIC.set_priority( - #device::Interrupt::#name, - rtfm::export::logical2hw(#priority, #nvic_prio_bits), - ); - )); - } - - // Set the cycle count to 0 and disable it while `init` executes - if cfg!(feature = "timer-queue") { - stmts.push(quote!(core.DWT.ctrl.modify(|r| r & !1);)); - stmts.push(quote!(core.DWT.cyccnt.write(0);)); - } - - stmts -} - -// This generates -// -// - at the root of the crate -// - a initResources struct (maybe) -// - a initLateResources struct (maybe) -// - a initLocals struct -// -// - an `init` module that contains -// - the `Context` struct -// - a re-export of the initResources struct -// - a re-export of the initLateResources struct -// - a re-export of the initLocals struct -// - the Spawn struct (maybe) -// - the Schedule struct (maybe, if `timer-queue` is enabled) -// -// - hidden in `const APP` -// - the initResources constructor -// -// - the user specified `init` function -// -// - a call to the user specified `init` function -fn init( - app: &App, - analysis: &Analysis, -) -> ( - // const_app - Option, - // mod_init - proc_macro2::TokenStream, - // init_locals - proc_macro2::TokenStream, - // init_resources - Option, - // init_late_resources - Option, - // user_init - proc_macro2::TokenStream, - // call_init - proc_macro2::TokenStream, -) { - let mut needs_lt = false; - let mut const_app = None; - let mut init_resources = None; - if !app.init.args.resources.is_empty() { - let (item, constructor) = resources_struct(Kind::Init, 0, &mut needs_lt, app, analysis); - - init_resources = Some(item); - const_app = Some(constructor); - } - - let core = if cfg!(feature = "timer-queue") { - quote!(rtfm::Peripherals { - CBP: core.CBP, - CPUID: core.CPUID, - DCB: &mut core.DCB, - FPB: core.FPB, - FPU: core.FPU, - ITM: core.ITM, - MPU: core.MPU, - SCB: &mut core.SCB, - TPIU: core.TPIU, - }) - } else { - quote!(rtfm::Peripherals { - CBP: core.CBP, - CPUID: core.CPUID, - DCB: core.DCB, - DWT: core.DWT, - FPB: core.FPB, - FPU: core.FPU, - ITM: core.ITM, - MPU: core.MPU, - SCB: &mut core.SCB, - SYST: core.SYST, - TPIU: core.TPIU, - }) - }; - - let call_init = quote!(let late = init(init::Locals::new(), init::Context::new(#core));); - - let late_fields = app - .resources - .iter() - .filter_map(|(name, res)| { - if res.expr.is_none() { - let ty = &res.ty; - - Some(quote!(pub #name: #ty)) - } else { - None - } - }) - .collect::>(); - - let attrs = &app.init.attrs; - let has_late_resources = !late_fields.is_empty(); - let (ret, init_late_resources) = if has_late_resources { - ( - Some(quote!(-> init::LateResources)), - Some(quote!( - /// Resources initialized at runtime - #[allow(non_snake_case)] - pub struct initLateResources { - #(#late_fields),* - } - )), - ) - } else { - (None, None) - }; - let context = &app.init.context; - let use_u32ext = if cfg!(feature = "timer-queue") { - Some(quote!( - use rtfm::U32Ext as _; - )) - } else { - None - }; - let (locals_struct, lets) = locals(Kind::Init, &app.init.statics); - let stmts = &app.init.stmts; - let user_init = quote!( - #(#attrs)* - #[allow(non_snake_case)] - fn init(__locals: init::Locals, #context: init::Context) #ret { - #use_u32ext - - #(#lets;)* - - #(#stmts)* - } - ); - - let mod_init = module( - Kind::Init, - (!app.init.args.resources.is_empty(), needs_lt), - !app.init.args.schedule.is_empty(), - !app.init.args.spawn.is_empty(), - has_late_resources, - app, - ); - - ( - const_app, - mod_init, - locals_struct, - init_resources, - init_late_resources, - user_init, - call_init, - ) -} - -/// Generates code that we must run after `init` returns. See comments inside -fn post_init(app: &App, analysis: &Analysis) -> Vec { - let mut stmts = vec![]; - - let device = &app.args.device; - let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS); - - // initialize late resources - for (name, res) in &app.resources { - if res.expr.is_some() { - continue; - } - - stmts.push(quote!(#name.as_mut_ptr().write(late.#name);)); - } - - // set exception priorities - for (handler, exception) in &app.exceptions { - let name = exception.args.binds(handler); - let priority = exception.args.priority; - - // compile time assert that this priority is supported by the device - stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];)); - - stmts.push(quote!(core.SCB.set_priority( - rtfm::export::SystemHandler::#name, - rtfm::export::logical2hw(#priority, #nvic_prio_bits), - );)); - } + let (const_app_resources, mod_resources) = resources::codegen(app, analysis, extra); - // set the system timer priority - if !analysis.timer_queue.tasks.is_empty() { - let priority = analysis.timer_queue.priority; + let (const_app_hardware_tasks, root_hardware_tasks, user_hardware_tasks) = + hardware_tasks::codegen(app, analysis, extra); - // compile time assert that this priority is supported by the device - stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];)); + let (const_app_software_tasks, root_software_tasks, user_software_tasks) = + software_tasks::codegen(app, analysis, extra); - stmts.push(quote!(core.SCB.set_priority( - rtfm::export::SystemHandler::SysTick, - rtfm::export::logical2hw(#priority, #nvic_prio_bits), - );)); - } + let const_app_dispatchers = dispatchers::codegen(app, analysis, extra); - if app.idle.is_none() { - // Set SLEEPONEXIT bit to enter sleep mode when returning from ISR - stmts.push(quote!(core.SCB.scr.modify(|r| r | 1 << 1);)); - } + let const_app_spawn = spawn::codegen(app, analysis, extra); - // enable and start the system timer - if !analysis.timer_queue.tasks.is_empty() { - stmts.push(quote!((*TQ.as_mut_ptr()) - .syst - .set_clock_source(rtfm::export::SystClkSource::Core);)); - stmts.push(quote!((*TQ.as_mut_ptr()).syst.enable_counter();)); - } - - // enable the cycle counter - if cfg!(feature = "timer-queue") { - stmts.push(quote!(core.DCB.enable_trace();)); - stmts.push(quote!(core.DWT.enable_cycle_counter();)); - } - - stmts.push(quote!(rtfm::export::interrupt::enable();)); - - stmts -} - -// If the user specified `idle` this generates -// -// - at the root of the crate -// - an idleResources struct (maybe) -// - an idleLocals struct -// -// - an `init` module that contains -// - the `Context` struct -// - a re-export of the idleResources struct -// - a re-export of the idleLocals struct -// - the Spawn struct (maybe) -// - the Schedule struct (maybe, if `timer-queue` is enabled) -// -// - hidden in `const APP` -// - the idleResources constructor -// -// - the user specified `idle` function -// -// - a call to the user specified `idle` function -// -// Otherwise it uses `loop { WFI }` as `idle` -fn idle( - app: &App, - analysis: &Analysis, -) -> ( - // const_app_idle - Option, - // mod_idle - Option, - // idle_locals - Option, - // idle_resources - Option, - // user_idle - Option, - // call_idle - proc_macro2::TokenStream, -) { - if let Some(idle) = app.idle.as_ref() { - let mut needs_lt = false; - let mut const_app = None; - let mut idle_resources = None; - - if !idle.args.resources.is_empty() { - let (item, constructor) = resources_struct(Kind::Idle, 0, &mut needs_lt, app, analysis); - - idle_resources = Some(item); - const_app = Some(constructor); - } - - let call_idle = quote!(idle( - idle::Locals::new(), - idle::Context::new(&rtfm::export::Priority::new(0)) - )); - - let attrs = &idle.attrs; - let context = &idle.context; - let use_u32ext = if cfg!(feature = "timer-queue") { - Some(quote!( - use rtfm::U32Ext as _; - )) - } else { - None - }; - let (idle_locals, lets) = locals(Kind::Idle, &idle.statics); - let stmts = &idle.stmts; - let user_idle = quote!( - #(#attrs)* - #[allow(non_snake_case)] - fn idle(__locals: idle::Locals, #context: idle::Context) -> ! { - #use_u32ext - use rtfm::Mutex as _; - - #(#lets;)* - - #(#stmts)* - } - ); + let const_app_timer_queue = timer_queue::codegen(app, analysis, extra); - let mod_idle = module( - Kind::Idle, - (!idle.args.resources.is_empty(), needs_lt), - !idle.args.schedule.is_empty(), - !idle.args.spawn.is_empty(), - false, - app, - ); + let const_app_schedule = schedule::codegen(app, extra); - ( - const_app, - Some(mod_idle), - Some(idle_locals), - idle_resources, - Some(user_idle), - call_idle, - ) - } else { - ( - None, - None, - None, - None, - None, - quote!(loop { - rtfm::export::wfi() - }), - ) - } -} - -/* Support functions */ -/// This function creates the `Resources` struct -/// -/// It's a bit unfortunate but this struct has to be created in the root because it refers to types -/// which may have been imported into the root. -fn resources_struct( - kind: Kind, - priority: u8, - needs_lt: &mut bool, - app: &App, - analysis: &Analysis, -) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { - let mut lt = None; - - let resources = match &kind { - Kind::Init => &app.init.args.resources, - Kind::Idle => &app.idle.as_ref().expect("UNREACHABLE").args.resources, - Kind::Interrupt(name) => &app.interrupts[name].args.resources, - Kind::Exception(name) => &app.exceptions[name].args.resources, - Kind::Task(name) => &app.tasks[name].args.resources, - }; - - let mut fields = vec![]; - let mut values = vec![]; - for name in resources { - let res = &app.resources[name]; - - let cfgs = &res.cfgs; - let mut_ = res.mutability; - let ty = &res.ty; - - if kind.is_init() { - if !analysis.ownerships.contains_key(name) { - // owned by `init` - fields.push(quote!( - #(#cfgs)* - pub #name: &'static #mut_ #ty - )); - - values.push(quote!( - #(#cfgs)* - #name: &#mut_ #name - )); - } else { - // owned by someone else - lt = Some(quote!('a)); - - fields.push(quote!( - #(#cfgs)* - pub #name: &'a mut #ty - )); - - values.push(quote!( - #(#cfgs)* - #name: &mut #name - )); - } - } else { - let ownership = &analysis.ownerships[name]; - - let mut exclusive = false; - if ownership.needs_lock(priority) { - if mut_.is_none() { - lt = Some(quote!('a)); - - fields.push(quote!( - #(#cfgs)* - pub #name: &'a #ty - )); - } else { - // resource proxy - lt = Some(quote!('a)); - - fields.push(quote!( - #(#cfgs)* - pub #name: resources::#name<'a> - )); - - values.push(quote!( - #(#cfgs)* - #name: resources::#name::new(priority) - )); - - continue; - } - } else { - let lt = if kind.runs_once() { - quote!('static) - } else { - lt = Some(quote!('a)); - quote!('a) - }; - - if ownership.is_owned() || mut_.is_none() { - fields.push(quote!( - #(#cfgs)* - pub #name: &#lt #mut_ #ty - )); - } else { - exclusive = true; - - fields.push(quote!( - #(#cfgs)* - pub #name: rtfm::Exclusive<#lt, #ty> - )); - } - } - - let is_late = res.expr.is_none(); - if is_late { - let expr = if mut_.is_some() { - quote!(&mut *#name.as_mut_ptr()) - } else { - quote!(&*#name.as_ptr()) - }; - - if exclusive { - values.push(quote!( - #(#cfgs)* - #name: rtfm::Exclusive(#expr) - )); - } else { - values.push(quote!( - #(#cfgs)* - #name: #expr - )); - } - } else { - if exclusive { - values.push(quote!( - #(#cfgs)* - #name: rtfm::Exclusive(&mut #name) - )); - } else { - values.push(quote!( - #(#cfgs)* - #name: &#mut_ #name - )); - } - } - } - } - - if lt.is_some() { - *needs_lt = true; - - // the struct could end up empty due to `cfg` leading to an error due to `'a` being unused - fields.push(quote!( - #[doc(hidden)] - pub __marker__: core::marker::PhantomData<&'a ()> - )); - - values.push(quote!(__marker__: core::marker::PhantomData)) - } - - let ident = kind.resources_ident(); - let doc = format!("Resources {} has access to", ident); - let item = quote!( - #[allow(non_snake_case)] - #[doc = #doc] - pub struct #ident<#lt> { - #(#fields,)* - } - ); - let arg = if kind.is_init() { - None - } else { - Some(quote!(priority: &#lt rtfm::export::Priority)) - }; - let constructor = quote!( - impl<#lt> #ident<#lt> { - #[inline(always)] - unsafe fn new(#arg) -> Self { - #ident { - #(#values,)* - } - } - } - ); - (item, constructor) -} - -/// Creates a `Mutex` implementation -fn impl_mutex( - app: &App, - cfgs: &[Attribute], - resources_prefix: bool, - name: &Ident, - ty: proc_macro2::TokenStream, - ceiling: u8, - ptr: proc_macro2::TokenStream, -) -> proc_macro2::TokenStream { - let path = if resources_prefix { - quote!(resources::#name) - } else { - quote!(#name) - }; - - let priority = if resources_prefix { - quote!(self.priority()) - } else { - quote!(self.priority) - }; - - let device = &app.args.device; + let name = &app.name; + let device = extra.device; quote!( - #(#cfgs)* - impl<'a> rtfm::Mutex for #path<'a> { - type T = #ty; - - #[inline(always)] - fn lock(&mut self, f: impl FnOnce(&mut #ty) -> R) -> R { - /// Priority ceiling - const CEILING: u8 = #ceiling; - - unsafe { - rtfm::export::lock( - #ptr, - #priority, - CEILING, - #device::NVIC_PRIO_BITS, - f, - ) - } - } - } - ) -} - -/// Creates a `Locals` struct and related code. This returns -/// -/// - `locals` -/// -/// ``` -/// pub struct Locals<'a> { -/// #[cfg(never)] -/// pub X: &'a mut X, -/// __marker__: PhantomData<&'a mut ()>, -/// } -/// ``` -/// -/// - `lt` -/// -/// ``` -/// 'a -/// ``` -/// -/// - `lets` -/// -/// ``` -/// #[cfg(never)] -/// let X = __locals.X -/// ``` -fn locals( - kind: Kind, - statics: &BTreeMap, -) -> ( - // locals - proc_macro2::TokenStream, - // lets - Vec, -) { - let runs_once = kind.runs_once(); - let ident = kind.locals_ident(); - - let mut lt = None; - let mut fields = vec![]; - let mut lets = vec![]; - let mut items = vec![]; - let mut values = vec![]; - for (name, static_) in statics { - let lt = if runs_once { - quote!('static) - } else { - lt = Some(quote!('a)); - quote!('a) - }; - - let cfgs = &static_.cfgs; - let expr = &static_.expr; - let ty = &static_.ty; - fields.push(quote!( - #(#cfgs)* - #name: &#lt mut #ty - )); - items.push(quote!( - #(#cfgs)* - static mut #name: #ty = #expr - )); - values.push(quote!( - #(#cfgs)* - #name: &mut #name - )); - lets.push(quote!( - #(#cfgs)* - let #name = __locals.#name - )); - } - - if lt.is_some() { - fields.push(quote!(__marker__: core::marker::PhantomData<&'a mut ()>)); - values.push(quote!(__marker__: core::marker::PhantomData)); - } + #(#user)* - let locals = quote!( - #[allow(non_snake_case)] - #[doc(hidden)] - pub struct #ident<#lt> { - #(#fields),* - } + #(#user_hardware_tasks)* - impl<#lt> #ident<#lt> { - #[inline(always)] - unsafe fn new() -> Self { - #(#items;)* + #(#user_software_tasks)* - #ident { - #(#values),* - } - } - } - ); - - (locals, lets) -} + #(#root)* -/// This function creates a module that contains -// -// - the Context struct -// - a re-export of the ${name}Resources struct (maybe) -// - a re-export of the ${name}LateResources struct (maybe) -// - a re-export of the ${name}Locals struct -// - the Spawn struct (maybe) -// - the Schedule struct (maybe, if `timer-queue` is enabled) -fn module( - kind: Kind, - resources: (/* has */ bool, /* 'a */ bool), - schedule: bool, - spawn: bool, - late_resources: bool, - app: &App, -) -> proc_macro2::TokenStream { - let mut items = vec![]; - let mut fields = vec![]; - let mut values = vec![]; + #(#mod_resources)* - let name = kind.ident(); + #(#root_hardware_tasks)* - let mut needs_instant = false; - let mut lt = None; - match kind { - Kind::Init => { - if cfg!(feature = "timer-queue") { - fields.push(quote!( - /// System start time = `Instant(0 /* cycles */)` - pub start: rtfm::Instant - )); + #(#root_software_tasks)* - values.push(quote!(start: rtfm::Instant::artificial(0))); - } - - let device = &app.args.device; - fields.push(quote!( - /// Core (Cortex-M) peripherals - pub core: rtfm::Peripherals<'a> - )); - fields.push(quote!( - /// Device specific peripherals - pub device: #device::Peripherals - )); - - values.push(quote!(core)); - values.push(quote!(device: #device::Peripherals::steal())); - lt = Some(quote!('a)); - } - - Kind::Idle => {} - - Kind::Exception(_) | Kind::Interrupt(_) => { - if cfg!(feature = "timer-queue") { - fields.push(quote!( - /// Time at which this handler started executing - pub start: rtfm::Instant - )); - - values.push(quote!(start: instant)); + /// Implementation details + // the user can't access the items within this `const` item + const #name: () = { + /// Always include the device crate which contains the vector table + use #device as _; - needs_instant = true; - } - } + #(#const_app)* - Kind::Task(_) => { - if cfg!(feature = "timer-queue") { - fields.push(quote!( - /// The time at which this task was scheduled to run - pub scheduled: rtfm::Instant - )); + #(#const_app_resources)* - values.push(quote!(scheduled: instant)); + #(#const_app_hardware_tasks)* - needs_instant = true; - } - } - } + #(#const_app_software_tasks)* - let ident = kind.locals_ident(); - items.push(quote!( - #[doc(inline)] - pub use super::#ident as Locals; - )); + #(#const_app_dispatchers)* - if resources.0 { - let ident = kind.resources_ident(); - let lt = if resources.1 { - lt = Some(quote!('a)); - Some(quote!('a)) - } else { - None - }; + #(#const_app_spawn)* - items.push(quote!( - #[doc(inline)] - pub use super::#ident as Resources; - )); + #(#const_app_timer_queue)* - fields.push(quote!( - /// Resources this task has access to - pub resources: Resources<#lt> - )); + #(#const_app_schedule)* - let priority = if kind.is_init() { - None - } else { - Some(quote!(priority)) + #(#mains)* }; - values.push(quote!(resources: Resources::new(#priority))); - } - - if schedule { - let doc = "Tasks that can be `schedule`-d from this context"; - if kind.is_init() { - items.push(quote!( - #[doc = #doc] - #[derive(Clone, Copy)] - pub struct Schedule { - _not_send: core::marker::PhantomData<*mut ()>, - } - )); - - fields.push(quote!( - #[doc = #doc] - pub schedule: Schedule - )); - - values.push(quote!( - schedule: Schedule { _not_send: core::marker::PhantomData } - )); - } else { - lt = Some(quote!('a)); - - items.push(quote!( - #[doc = #doc] - #[derive(Clone, Copy)] - pub struct Schedule<'a> { - priority: &'a rtfm::export::Priority, - } - - impl<'a> Schedule<'a> { - #[doc(hidden)] - #[inline(always)] - pub unsafe fn priority(&self) -> &rtfm::export::Priority { - &self.priority - } - } - )); - - fields.push(quote!( - #[doc = #doc] - pub schedule: Schedule<'a> - )); - - values.push(quote!( - schedule: Schedule { priority } - )); - } - } - - if spawn { - let doc = "Tasks that can be `spawn`-ed from this context"; - if kind.is_init() { - fields.push(quote!( - #[doc = #doc] - pub spawn: Spawn - )); - - items.push(quote!( - #[doc = #doc] - #[derive(Clone, Copy)] - pub struct Spawn { - _not_send: core::marker::PhantomData<*mut ()>, - } - )); - - values.push(quote!(spawn: Spawn { _not_send: core::marker::PhantomData })); - } else { - lt = Some(quote!('a)); - - fields.push(quote!( - #[doc = #doc] - pub spawn: Spawn<'a> - )); - - let mut instant_method = None; - if kind.is_idle() { - items.push(quote!( - #[doc = #doc] - #[derive(Clone, Copy)] - pub struct Spawn<'a> { - priority: &'a rtfm::export::Priority, - } - )); - - values.push(quote!(spawn: Spawn { priority })); - } else { - let instant_field = if cfg!(feature = "timer-queue") { - needs_instant = true; - instant_method = Some(quote!( - pub unsafe fn instant(&self) -> rtfm::Instant { - self.instant - } - )); - Some(quote!(instant: rtfm::Instant,)) - } else { - None - }; - - items.push(quote!( - /// Tasks that can be spawned from this context - #[derive(Clone, Copy)] - pub struct Spawn<'a> { - #instant_field - priority: &'a rtfm::export::Priority, - } - )); - - let _instant = if needs_instant { - Some(quote!(, instant)) - } else { - None - }; - values.push(quote!( - spawn: Spawn { priority #_instant } - )); - } - - items.push(quote!( - impl<'a> Spawn<'a> { - #[doc(hidden)] - #[inline(always)] - pub unsafe fn priority(&self) -> &rtfm::export::Priority { - self.priority - } - - #instant_method - } - )); - } - } - - if late_resources { - items.push(quote!( - #[doc(inline)] - pub use super::initLateResources as LateResources; - )); - } - - let doc = match kind { - Kind::Exception(_) => "Hardware task (exception)", - Kind::Idle => "Idle loop", - Kind::Init => "Initialization function", - Kind::Interrupt(_) => "Hardware task (interrupt)", - Kind::Task(_) => "Software task", - }; - - let core = if kind.is_init() { - lt = Some(quote!('a)); - Some(quote!(core: rtfm::Peripherals<'a>,)) - } else { - None - }; - - let priority = if kind.is_init() { - None - } else { - Some(quote!(priority: &#lt rtfm::export::Priority)) - }; - - let instant = if needs_instant { - Some(quote!(, instant: rtfm::Instant)) - } else { - None - }; - items.push(quote!( - /// Execution context - pub struct Context<#lt> { - #(#fields,)* - } - - impl<#lt> Context<#lt> { - #[inline(always)] - pub unsafe fn new(#core #priority #instant) -> Self { - Context { - #(#values,)* - } - } - } - )); - - if !items.is_empty() { - quote!( - #[allow(non_snake_case)] - #[doc = #doc] - pub mod #name { - #(#items)* - } - ) - } else { - quote!() - } -} - -/// Creates the body of `spawn_${name}` -fn mk_spawn_body<'a>( - spawner: &Ident, - name: &Ident, - app: &'a App, - analysis: &Analysis, -) -> proc_macro2::TokenStream { - let spawner_is_init = spawner == "init"; - let device = &app.args.device; - - let spawnee = &app.tasks[name]; - let priority = spawnee.args.priority; - let dispatcher = &analysis.dispatchers[&priority].interrupt; - - let (_, tupled, _, _) = regroup_inputs(&spawnee.inputs); - - let inputs = mk_inputs_ident(name); - let fq = mk_fq_ident(name); - - let rq = mk_rq_ident(priority); - let t = mk_t_ident(priority); - - let write_instant = if cfg!(feature = "timer-queue") { - let instants = mk_instants_ident(name); - - Some(quote!( - #instants.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(instant); - )) - } else { - None - }; - - let (dequeue, enqueue) = if spawner_is_init { - // `init` has exclusive access to these queues so we can bypass the resources AND - // the consumer / producer split - ( - quote!(#fq.dequeue()), - quote!(#rq.enqueue_unchecked((#t::#name, index));), - ) - } else { - ( - quote!((#fq { priority }).lock(|fq| fq.split().1.dequeue())), - quote!((#rq { priority }).lock(|rq| { - rq.split().0.enqueue_unchecked((#t::#name, index)) - });), - ) - }; - - quote!( - unsafe { - use rtfm::Mutex as _; - - let input = #tupled; - if let Some(index) = #dequeue { - #inputs.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(input); - - #write_instant - - #enqueue - - rtfm::pend(#device::Interrupt::#dispatcher); - - Ok(()) - } else { - Err(input) - } - } ) } - -/// Creates the body of `schedule_${name}` -fn mk_schedule_body<'a>(scheduler: &Ident, name: &Ident, app: &'a App) -> proc_macro2::TokenStream { - let scheduler_is_init = scheduler == "init"; - - let schedulee = &app.tasks[name]; - - let (_, tupled, _, _) = regroup_inputs(&schedulee.inputs); - - let fq = mk_fq_ident(name); - let inputs = mk_inputs_ident(name); - let instants = mk_instants_ident(name); - - let (dequeue, enqueue) = if scheduler_is_init { - // `init` has exclusive access to these queues so we can bypass the resources AND - // the consumer / producer split - let dequeue = quote!(#fq.dequeue()); - - (dequeue, quote!((*TQ.as_mut_ptr()).enqueue_unchecked(nr);)) - } else { - ( - quote!((#fq { priority }).lock(|fq| fq.split().1.dequeue())), - quote!((TQ { priority }).lock(|tq| tq.enqueue_unchecked(nr));), - ) - }; - - quote!( - unsafe { - use rtfm::Mutex as _; - - let input = #tupled; - if let Some(index) = #dequeue { - #instants.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(instant); - - #inputs.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(input); - - let nr = rtfm::export::NotReady { - instant, - index, - task: T::#name, - }; - - #enqueue - - Ok(()) - } else { - Err(input) - } - } - ) -} - -/// `u8` -> (unsuffixed) `LitInt` -fn mk_capacity_literal(capacity: u8) -> LitInt { - LitInt::new(u64::from(capacity), IntSuffix::None, Span::call_site()) -} - -/// e.g. `4u8` -> `U4` -fn mk_typenum_capacity(capacity: u8, power_of_two: bool) -> proc_macro2::TokenStream { - let capacity = if power_of_two { - capacity - .checked_next_power_of_two() - .expect("capacity.next_power_of_two()") - } else { - capacity - }; - - let ident = Ident::new(&format!("U{}", capacity), Span::call_site()); - - quote!(rtfm::export::consts::#ident) -} - -/// e.g. `foo` -> `foo_INPUTS` -fn mk_inputs_ident(base: &Ident) -> Ident { - Ident::new(&format!("{}_INPUTS", base), Span::call_site()) -} - -/// e.g. `foo` -> `foo_INSTANTS` -fn mk_instants_ident(base: &Ident) -> Ident { - Ident::new(&format!("{}_INSTANTS", base), Span::call_site()) -} - -/// e.g. `foo` -> `foo_FQ` -fn mk_fq_ident(base: &Ident) -> Ident { - Ident::new(&format!("{}_FQ", base), Span::call_site()) -} - -/// e.g. `3` -> `RQ3` -fn mk_rq_ident(level: u8) -> Ident { - Ident::new(&format!("RQ{}", level), Span::call_site()) -} - -/// e.g. `3` -> `T3` -fn mk_t_ident(level: u8) -> Ident { - Ident::new(&format!("T{}", level), Span::call_site()) -} - -fn mk_spawn_ident(task: &Ident) -> Ident { - Ident::new(&format!("spawn_{}", task), Span::call_site()) -} - -fn mk_schedule_ident(task: &Ident) -> Ident { - Ident::new(&format!("schedule_{}", task), Span::call_site()) -} - -// Regroups a task inputs -// -// e.g. &[`input: Foo`], &[`mut x: i32`, `ref y: i64`] -fn regroup_inputs( - inputs: &[ArgCaptured], -) -> ( - // args e.g. &[`_0`], &[`_0: i32`, `_1: i64`] - Vec, - // tupled e.g. `_0`, `(_0, _1)` - proc_macro2::TokenStream, - // untupled e.g. &[`_0`], &[`_0`, `_1`] - Vec, - // ty e.g. `Foo`, `(i32, i64)` - proc_macro2::TokenStream, -) { - if inputs.len() == 1 { - let ty = &inputs[0].ty; - - ( - vec![quote!(_0: #ty)], - quote!(_0), - vec![quote!(_0)], - quote!(#ty), - ) - } else { - let mut args = vec![]; - let mut pats = vec![]; - let mut tys = vec![]; - - for (i, input) in inputs.iter().enumerate() { - let i = Ident::new(&format!("_{}", i), Span::call_site()); - let ty = &input.ty; - - args.push(quote!(#i: #ty)); - - pats.push(quote!(#i)); - - tys.push(quote!(#ty)); - } - - let tupled = { - let pats = pats.clone(); - quote!((#(#pats,)*)) - }; - let ty = quote!((#(#tys,)*)); - (args, tupled, pats, ty) - } -} - -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -enum Kind { - Exception(Ident), - Idle, - Init, - Interrupt(Ident), - Task(Ident), -} - -impl Kind { - fn ident(&self) -> Ident { - let span = Span::call_site(); - match self { - Kind::Init => Ident::new("init", span), - Kind::Idle => Ident::new("idle", span), - Kind::Task(name) | Kind::Interrupt(name) | Kind::Exception(name) => name.clone(), - } - } - - fn locals_ident(&self) -> Ident { - Ident::new(&format!("{}Locals", self.ident()), Span::call_site()) - } - - fn resources_ident(&self) -> Ident { - Ident::new(&format!("{}Resources", self.ident()), Span::call_site()) - } - - fn is_idle(&self) -> bool { - *self == Kind::Idle - } - - fn is_init(&self) -> bool { - *self == Kind::Init - } - - fn runs_once(&self) -> bool { - match *self { - Kind::Init | Kind::Idle => true, - _ => false, - } - } -} diff --git a/macros/src/codegen/assertions.rs b/macros/src/codegen/assertions.rs new file mode 100644 index 0000000..95268a2 --- /dev/null +++ b/macros/src/codegen/assertions.rs @@ -0,0 +1,26 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +use crate::analyze::Analysis; + +/// Generates compile-time assertions that check that types implement the `Send` / `Sync` traits +pub fn codegen(core: u8, analysis: &Analysis) -> Vec { + let mut stmts = vec![]; + + // we don't generate *all* assertions on all cores because the user could conditionally import a + // type only on some core (e.g. `#[cfg(core = "0")] use some::Type;`) + + if let Some(types) = analysis.send_types.get(&core) { + for ty in types { + stmts.push(quote!(rtfm::export::assert_send::<#ty>();)); + } + } + + if let Some(types) = analysis.sync_types.get(&core) { + for ty in types { + stmts.push(quote!(rtfm::export::assert_sync::<#ty>();)); + } + } + + stmts +} diff --git a/macros/src/codegen/dispatchers.rs b/macros/src/codegen/dispatchers.rs new file mode 100644 index 0000000..65d25c7 --- /dev/null +++ b/macros/src/codegen/dispatchers.rs @@ -0,0 +1,178 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::ast::App; + +use crate::{analyze::Analysis, check::Extra, codegen::util}; + +/// Generates task dispatchers +pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec { + let mut items = vec![]; + + for (&receiver, dispatchers) in &analysis.channels { + let interrupts = &analysis.interrupts[&receiver]; + + for (&level, channels) in dispatchers { + let mut stmts = vec![]; + + for (&sender, channel) in channels { + let cfg_sender = util::cfg_core(sender, app.args.cores); + + let variants = channel + .tasks + .iter() + .map(|name| { + let cfgs = &app.software_tasks[name].cfgs; + + quote!( + #(#cfgs)* + #name + ) + }) + .collect::>(); + + let doc = format!( + "Software tasks spawned from core #{} to be dispatched at priority level {} by core #{}", + sender, level, receiver, + ); + let t = util::spawn_t_ident(receiver, level, sender); + items.push(quote!( + #[allow(non_camel_case_types)] + #[derive(Clone, Copy)] + #[doc = #doc] + enum #t { + #(#variants,)* + } + )); + + let n = util::capacity_typenum(channel.capacity, true); + let rq = util::rq_ident(receiver, level, sender); + let (rq_attr, rq_ty, rq_expr) = if sender == receiver { + ( + cfg_sender.clone(), + quote!(rtfm::export::SCRQ<#t, #n>), + quote!(rtfm::export::Queue(unsafe { + rtfm::export::iQueue::u8_sc() + })), + ) + } else { + ( + Some(quote!(#[rtfm::export::shared])), + quote!(rtfm::export::MCRQ<#t, #n>), + quote!(rtfm::export::Queue(rtfm::export::iQueue::u8())), + ) + }; + + let doc = format!( + "Queue of tasks sent by core #{} ready to be dispatched by core #{} at priority level {}", + sender, + receiver, + level + ); + items.push(quote!( + #[doc = #doc] + #rq_attr + static mut #rq: #rq_ty = #rq_expr; + )); + + if let Some(ceiling) = channel.ceiling { + items.push(quote!( + #cfg_sender + struct #rq<'a> { + priority: &'a rtfm::export::Priority, + } + )); + + items.push(util::impl_mutex( + extra, + &[], + cfg_sender.as_ref(), + false, + &rq, + rq_ty, + ceiling, + quote!(&mut #rq), + )); + } + + let arms = channel + .tasks + .iter() + .map(|name| { + let task = &app.software_tasks[name]; + let cfgs = &task.cfgs; + let fq = util::fq_ident(name, sender); + let inputs = util::inputs_ident(name, sender); + let (_, tupled, pats, _) = util::regroup_inputs(&task.inputs); + + let (let_instant, instant) = if app.uses_schedule(receiver) { + let instants = util::instants_ident(name, sender); + + ( + quote!( + let instant = + #instants.get_unchecked(usize::from(index)).as_ptr().read(); + ), + quote!(, instant), + ) + } else { + (quote!(), quote!()) + }; + + let locals_new = if task.locals.is_empty() { + quote!() + } else { + quote!(#name::Locals::new(),) + }; + + quote!( + #(#cfgs)* + #t::#name => { + let #tupled = + #inputs.get_unchecked(usize::from(index)).as_ptr().read(); + #let_instant + #fq.split().0.enqueue_unchecked(index); + let priority = &rtfm::export::Priority::new(PRIORITY); + #name( + #locals_new + #name::Context::new(priority #instant) + #(,#pats)* + ) + } + ) + }) + .collect::>(); + + stmts.push(quote!( + while let Some((task, index)) = #rq.split().1.dequeue() { + match task { + #(#arms)* + } + } + )); + } + + let doc = format!( + "Interrupt handler used by core #{} to dispatch tasks at priority {}", + receiver, level + ); + let cfg_receiver = util::cfg_core(receiver, app.args.cores); + let interrupt = &interrupts[&level]; + items.push(quote!( + #[allow(non_snake_case)] + #[doc = #doc] + #[no_mangle] + #cfg_receiver + unsafe fn #interrupt() { + /// The priority of this interrupt handler + const PRIORITY: u8 = #level; + + rtfm::export::run(PRIORITY, || { + #(#stmts)* + }); + } + )); + } + } + + items +} diff --git a/macros/src/codegen/hardware_tasks.rs b/macros/src/codegen/hardware_tasks.rs new file mode 100644 index 0000000..e65bad5 --- /dev/null +++ b/macros/src/codegen/hardware_tasks.rs @@ -0,0 +1,121 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::{ast::App, Context}; + +use crate::{ + analyze::Analysis, + check::Extra, + codegen::{locals, module, resources_struct, util}, +}; + +/// Generate support code for hardware tasks (`#[exception]`s and `#[interrupt]`s) +pub fn codegen( + app: &App, + analysis: &Analysis, + extra: &Extra, +) -> ( + // const_app_hardware_tasks -- interrupt handlers and `${task}Resources` constructors + Vec, + // root_hardware_tasks -- items that must be placed in the root of the crate: + // - `${task}Locals` structs + // - `${task}Resources` structs + // - `${task}` modules + Vec, + // user_hardware_tasks -- the `#[task]` functions written by the user + Vec, +) { + let mut const_app = vec![]; + let mut root = vec![]; + let mut user_tasks = vec![]; + + for (name, task) in &app.hardware_tasks { + let core = task.args.core; + let cfg_core = util::cfg_core(core, app.args.cores); + + let (let_instant, instant) = if app.uses_schedule(core) { + let m = extra.monotonic(); + + ( + Some(quote!(let instant = <#m as rtfm::Monotonic>::now();)), + Some(quote!(, instant)), + ) + } else { + (None, None) + }; + + let locals_new = if task.locals.is_empty() { + quote!() + } else { + quote!(#name::Locals::new(),) + }; + + let symbol = task.args.binds(name); + let priority = task.args.priority; + + const_app.push(quote!( + #[allow(non_snake_case)] + #[no_mangle] + #cfg_core + unsafe fn #symbol() { + const PRIORITY: u8 = #priority; + + #let_instant + + rtfm::export::run(PRIORITY, || { + crate::#name( + #locals_new + #name::Context::new(&rtfm::export::Priority::new(PRIORITY) #instant) + ) + }); + } + )); + + let mut needs_lt = false; + + // `${task}Resources` + if !task.args.resources.is_empty() { + let (item, constructor) = resources_struct::codegen( + Context::HardwareTask(name), + priority, + &mut needs_lt, + app, + analysis, + ); + + root.push(item); + + const_app.push(constructor); + } + + root.push(module::codegen( + Context::HardwareTask(name), + needs_lt, + app, + extra, + )); + + // `${task}Locals` + let mut locals_pat = None; + if !task.locals.is_empty() { + let (struct_, pat) = locals::codegen(Context::HardwareTask(name), &task.locals, app); + + root.push(struct_); + locals_pat = Some(pat); + } + + let attrs = &task.attrs; + let context = &task.context; + let stmts = &task.stmts; + user_tasks.push(quote!( + #(#attrs)* + #[allow(non_snake_case)] + fn #name(#(#locals_pat,)* #context: #name::Context) { + use rtfm::Mutex as _; + + #(#stmts)* + } + )); + } + + (const_app, root, user_tasks) +} diff --git a/macros/src/codegen/idle.rs b/macros/src/codegen/idle.rs new file mode 100644 index 0000000..7af01c9 --- /dev/null +++ b/macros/src/codegen/idle.rs @@ -0,0 +1,92 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::{ast::App, Context}; + +use crate::{ + analyze::Analysis, + check::Extra, + codegen::{locals, module, resources_struct, util}, +}; + +/// Generates support code for `#[idle]` functions +pub fn codegen( + core: u8, + app: &App, + analysis: &Analysis, + extra: &Extra, +) -> ( + // const_app_idle -- the `${idle}Resources` constructor + Option, + // root_idle -- items that must be placed in the root of the crate: + // - the `${idle}Locals` struct + // - the `${idle}Resources` struct + // - the `${idle}` module, which contains types like `${idle}::Context` + Vec, + // user_idle + Option, + // call_idle + TokenStream2, +) { + if let Some(idle) = app.idles.get(&core) { + let mut needs_lt = false; + let mut const_app = None; + let mut root_idle = vec![]; + let mut locals_pat = None; + let mut locals_new = None; + + if !idle.args.resources.is_empty() { + let (item, constructor) = + resources_struct::codegen(Context::Idle(core), 0, &mut needs_lt, app, analysis); + + root_idle.push(item); + const_app = Some(constructor); + } + + let name = &idle.name; + if !idle.locals.is_empty() { + let (locals, pat) = locals::codegen(Context::Idle(core), &idle.locals, app); + + locals_new = Some(quote!(#name::Locals::new())); + locals_pat = Some(pat); + root_idle.push(locals); + } + + root_idle.push(module::codegen(Context::Idle(core), needs_lt, app, extra)); + + let cfg_core = util::cfg_core(core, app.args.cores); + let attrs = &idle.attrs; + let context = &idle.context; + let stmts = &idle.stmts; + let user_idle = Some(quote!( + #cfg_core + #(#attrs)* + #[allow(non_snake_case)] + fn #name(#(#locals_pat,)* #context: #name::Context) -> ! { + use rtfm::Mutex as _; + + #(#stmts)* + } + )); + + let call_idle = quote!(#name( + #(#locals_new,)* + #name::Context::new(&rtfm::export::Priority::new(0)) + )); + + ( + const_app, + root_idle, + user_idle, + call_idle, + ) + } else { + ( + None, + vec![], + None, + quote!(loop { + rtfm::export::wfi() + }), + ) + } +} diff --git a/macros/src/codegen/init.rs b/macros/src/codegen/init.rs new file mode 100644 index 0000000..271be94 --- /dev/null +++ b/macros/src/codegen/init.rs @@ -0,0 +1,112 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::{ast::App, Context}; + +use crate::{ + analyze::Analysis, + check::Extra, + codegen::{locals, module, resources_struct, util}, +}; + +/// Generates support code for `#[init]` functions +pub fn codegen( + core: u8, + app: &App, + analysis: &Analysis, + extra: &Extra, +) -> ( + // const_app_idle -- the `${init}Resources` constructor + Option, + // root_init -- items that must be placed in the root of the crate: + // - the `${init}Locals` struct + // - the `${init}Resources` struct + // - the `${init}LateResources` struct + // - the `${init}` module, which contains types like `${init}::Context` + Vec, + // user_init -- the `#[init]` function written by the user + Option, + // call_init -- the call to the user `#[init]` if there's one + Option, +) { + if let Some(init) = app.inits.get(&core) { + let cfg_core = util::cfg_core(core, app.args.cores); + let mut needs_lt = false; + let name = &init.name; + + let mut root_init = vec![]; + + let ret = { + let late_fields = analysis + .late_resources + .get(&core) + .map(|resources| { + resources + .iter() + .map(|name| { + let ty = &app.late_resources[name].ty; + + quote!(pub #name: #ty) + }) + .collect::>() + }) + .unwrap_or(vec![]); + + if !late_fields.is_empty() { + let late_resources = util::late_resources_ident(&name); + + root_init.push(quote!( + /// Resources initialized at runtime + #cfg_core + #[allow(non_snake_case)] + pub struct #late_resources { + #(#late_fields),* + } + )); + + Some(quote!(-> #name::LateResources)) + } else { + None + } + }; + + let mut locals_pat = None; + let mut locals_new = None; + if !init.locals.is_empty() { + let (struct_, pat) = locals::codegen(Context::Init(core), &init.locals, app); + + locals_new = Some(quote!(#name::Locals::new())); + locals_pat = Some(pat); + root_init.push(struct_); + } + + let context = &init.context; + let attrs = &init.attrs; + let stmts = &init.stmts; + let user_init = Some(quote!( + #(#attrs)* + #cfg_core + #[allow(non_snake_case)] + fn #name(#(#locals_pat,)* #context: #name::Context) #ret { + #(#stmts)* + } + )); + + let mut const_app = None; + if !init.args.resources.is_empty() { + let (item, constructor) = + resources_struct::codegen(Context::Init(core), 0, &mut needs_lt, app, analysis); + + root_init.push(item); + const_app = Some(constructor); + } + + let call_init = + Some(quote!(let late = #name(#(#locals_new,)* #name::Context::new(core.into()));)); + + root_init.push(module::codegen(Context::Init(core), needs_lt, app, extra)); + + (const_app, root_init, user_init, call_init) + } else { + (None, vec![], None, None) + } +} diff --git a/macros/src/codegen/locals.rs b/macros/src/codegen/locals.rs new file mode 100644 index 0000000..9663563 --- /dev/null +++ b/macros/src/codegen/locals.rs @@ -0,0 +1,94 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::{ + ast::{App, Local}, + Context, Map, +}; + +use crate::codegen::util; + +pub fn codegen( + ctxt: Context, + locals: &Map, + app: &App, +) -> ( + // locals + TokenStream2, + // pat + TokenStream2, +) { + assert!(!locals.is_empty()); + + let runs_once = ctxt.runs_once(); + let ident = util::locals_ident(ctxt, app); + + let mut lt = None; + let mut fields = vec![]; + let mut items = vec![]; + let mut names = vec![]; + let mut values = vec![]; + let mut pats = vec![]; + let mut has_cfgs = false; + + for (name, local) in locals { + let lt = if runs_once { + quote!('static) + } else { + lt = Some(quote!('a)); + quote!('a) + }; + + let cfgs = &local.cfgs; + has_cfgs |= !cfgs.is_empty(); + + let expr = &local.expr; + let ty = &local.ty; + fields.push(quote!( + #(#cfgs)* + #name: &#lt mut #ty + )); + items.push(quote!( + #(#cfgs)* + static mut #name: #ty = #expr + )); + values.push(quote!( + #(#cfgs)* + #name: &mut #name + )); + names.push(name); + pats.push(quote!( + #(#cfgs)* + #name + )); + } + + if lt.is_some() && has_cfgs { + fields.push(quote!(__marker__: core::marker::PhantomData<&'a mut ()>)); + values.push(quote!(__marker__: core::marker::PhantomData)); + } + + let locals = quote!( + #[allow(non_snake_case)] + #[doc(hidden)] + pub struct #ident<#lt> { + #(#fields),* + } + + impl<#lt> #ident<#lt> { + #[inline(always)] + unsafe fn new() -> Self { + #(#items;)* + + #ident { + #(#values),* + } + } + } + ); + + let ident = ctxt.ident(app); + ( + locals, + quote!(#ident::Locals { #(#pats,)* .. }: #ident::Locals), + ) +} diff --git a/macros/src/codegen/module.rs b/macros/src/codegen/module.rs new file mode 100644 index 0000000..5f077a2 --- /dev/null +++ b/macros/src/codegen/module.rs @@ -0,0 +1,328 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::{ast::App, Context}; + +use crate::{check::Extra, codegen::util}; + +pub fn codegen(ctxt: Context, resources_tick: bool, app: &App, extra: &Extra) -> TokenStream2 { + let mut items = vec![]; + let mut fields = vec![]; + let mut values = vec![]; + + let name = ctxt.ident(app); + + let core = ctxt.core(app); + let mut needs_instant = false; + let mut lt = None; + match ctxt { + Context::Init(core) => { + if app.uses_schedule(core) { + let m = extra.monotonic(); + + fields.push(quote!( + /// System start time = `Instant(0 /* cycles */)` + pub start: <#m as rtfm::Monotonic>::Instant + )); + + values.push(quote!(start: <#m as rtfm::Monotonic>::zero())); + + fields.push(quote!( + /// Core (Cortex-M) peripherals minus the SysTick + pub core: rtfm::Peripherals + )); + } else { + fields.push(quote!( + /// Core (Cortex-M) peripherals + pub core: rtfm::export::Peripherals + )); + } + + if extra.peripherals == Some(core) { + let device = extra.device; + + fields.push(quote!( + /// Device peripherals + pub device: #device::Peripherals + )); + + values.push(quote!(device: #device::Peripherals::steal())); + } + + values.push(quote!(core)); + } + + Context::Idle(..) => {} + + Context::HardwareTask(..) => { + if app.uses_schedule(core) { + let m = extra.monotonic(); + + fields.push(quote!( + /// Time at which this handler started executing + pub start: <#m as rtfm::Monotonic>::Instant + )); + + values.push(quote!(start: instant)); + + needs_instant = true; + } + } + + Context::SoftwareTask(..) => { + if app.uses_schedule(core) { + let m = extra.monotonic(); + + fields.push(quote!( + /// The time at which this task was scheduled to run + pub scheduled: <#m as rtfm::Monotonic>::Instant + )); + + values.push(quote!(scheduled: instant)); + + needs_instant = true; + } + } + } + + if ctxt.has_locals(app) { + let ident = util::locals_ident(ctxt, app); + items.push(quote!( + #[doc(inline)] + pub use super::#ident as Locals; + )); + } + + if ctxt.has_resources(app) { + let ident = util::resources_ident(ctxt, app); + let lt = if resources_tick { + lt = Some(quote!('a)); + Some(quote!('a)) + } else { + None + }; + + items.push(quote!( + #[doc(inline)] + pub use super::#ident as Resources; + )); + + fields.push(quote!( + /// Resources this task has access to + pub resources: Resources<#lt> + )); + + let priority = if ctxt.is_init() { + None + } else { + Some(quote!(priority)) + }; + values.push(quote!(resources: Resources::new(#priority))); + } + + if ctxt.uses_schedule(app) { + let doc = "Tasks that can be `schedule`-d from this context"; + if ctxt.is_init() { + items.push(quote!( + #[doc = #doc] + #[derive(Clone, Copy)] + pub struct Schedule { + _not_send: core::marker::PhantomData<*mut ()>, + } + )); + + fields.push(quote!( + #[doc = #doc] + pub schedule: Schedule + )); + + values.push(quote!( + schedule: Schedule { _not_send: core::marker::PhantomData } + )); + } else { + lt = Some(quote!('a)); + + items.push(quote!( + #[doc = #doc] + #[derive(Clone, Copy)] + pub struct Schedule<'a> { + priority: &'a 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 ctxt.uses_spawn(app) { + let doc = "Tasks that can be `spawn`-ed from this context"; + if ctxt.is_init() { + fields.push(quote!( + #[doc = #doc] + pub spawn: Spawn + )); + + items.push(quote!( + #[doc = #doc] + #[derive(Clone, Copy)] + pub struct Spawn { + _not_send: core::marker::PhantomData<*mut ()>, + } + )); + + values.push(quote!(spawn: Spawn { _not_send: core::marker::PhantomData })); + } else { + lt = Some(quote!('a)); + + fields.push(quote!( + #[doc = #doc] + pub spawn: Spawn<'a> + )); + + let mut instant_method = None; + if ctxt.is_idle() { + items.push(quote!( + #[doc = #doc] + #[derive(Clone, Copy)] + pub struct Spawn<'a> { + priority: &'a rtfm::export::Priority, + } + )); + + values.push(quote!(spawn: Spawn { priority })); + } else { + let instant_field = if app.uses_schedule(core) { + let m = extra.monotonic(); + + needs_instant = true; + instant_method = Some(quote!( + pub unsafe fn instant(&self) -> <#m as rtfm::Monotonic>::Instant { + self.instant + } + )); + Some(quote!(instant: <#m as rtfm::Monotonic>::Instant,)) + } else { + None + }; + + items.push(quote!( + /// Tasks that can be spawned from this context + #[derive(Clone, Copy)] + pub struct Spawn<'a> { + #instant_field + priority: &'a 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 let Context::Init(core) = ctxt { + let init = &app.inits[&core]; + if init.returns_late_resources { + let late_resources = util::late_resources_ident(&init.name); + + items.push(quote!( + #[doc(inline)] + pub use super::#late_resources as LateResources; + )); + } + } + + let doc = match ctxt { + Context::Idle(_) => "Idle loop", + Context::Init(_) => "Initialization function", + Context::HardwareTask(_) => "Hardware task", + Context::SoftwareTask(_) => "Software task", + }; + + let core = if ctxt.is_init() { + if app.uses_schedule(core) { + Some(quote!(core: rtfm::Peripherals,)) + } else { + Some(quote!(core: rtfm::export::Peripherals,)) + } + } else { + None + }; + + let priority = if ctxt.is_init() { + None + } else { + Some(quote!(priority: &#lt rtfm::export::Priority)) + }; + + let instant = if needs_instant { + let m = extra.monotonic(); + + Some(quote!(, instant: <#m as rtfm::Monotonic>::Instant)) + } else { + None + }; + + items.push(quote!( + /// Execution context + pub struct Context<#lt> { + #(#fields,)* + } + + impl<#lt> Context<#lt> { + #[inline(always)] + pub unsafe fn new(#core #priority #instant) -> Self { + Context { + #(#values,)* + } + } + } + )); + + if !items.is_empty() { + let cfg_core = util::cfg_core(ctxt.core(app), app.args.cores); + + quote!( + #[allow(non_snake_case)] + #[doc = #doc] + #cfg_core + pub mod #name { + #(#items)* + } + ) + } else { + quote!() + } +} diff --git a/macros/src/codegen/post_init.rs b/macros/src/codegen/post_init.rs new file mode 100644 index 0000000..f492d31 --- /dev/null +++ b/macros/src/codegen/post_init.rs @@ -0,0 +1,139 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +use crate::{analyze::Analysis, check::Extra, codegen::util}; + +/// Generates code that runs after `#[init]` returns +pub fn codegen( + core: u8, + analysis: &Analysis, + extra: &Extra, +) -> (Vec, Vec) { + let mut const_app = vec![]; + let mut stmts = vec![]; + + // initialize late resources + if let Some(late_resources) = analysis.late_resources.get(&core) { + for name in late_resources { + // if it's live + if analysis.locations.get(name).is_some() { + stmts.push(quote!(#name.as_mut_ptr().write(late.#name);)); + } + } + } + + if analysis.timer_queues.is_empty() { + // cross-initialization barriers -- notify *other* cores that their resources have been + // initialized + if analysis.initialization_barriers.contains_key(&core) { + let ib = util::init_barrier(core); + + const_app.push(quote!( + #[rtfm::export::shared] + static #ib: rtfm::export::Barrier = rtfm::export::Barrier::new(); + )); + + stmts.push(quote!( + #ib.release(); + )); + } + + // then wait until the other cores have initialized *our* resources + for (&initializer, users) in &analysis.initialization_barriers { + if users.contains(&core) { + let ib = util::init_barrier(initializer); + + stmts.push(quote!( + #ib.wait(); + )); + } + } + + // cross-spawn barriers: wait until other cores are ready to receive messages + for (&receiver, senders) in &analysis.spawn_barriers { + if senders.get(&core) == Some(&false) { + let sb = util::spawn_barrier(receiver); + + stmts.push(quote!( + #sb.wait(); + )); + } + } + } else { + // if the `schedule` API is used then we'll synchronize all cores to leave the + // `init`-ialization phase at the same time. In this case the rendezvous barrier makes the + // cross-initialization and spawn barriers unnecessary + + let m = extra.monotonic(); + + if analysis.timer_queues.len() == 1 { + // reset the monotonic timer / counter + stmts.push(quote!( + <#m as rtfm::Monotonic>::reset(); + )); + } else { + // in the multi-core case we need a rendezvous (RV) barrier between *all* the cores that + // use the `schedule` API; otherwise one of the cores could observe the before-reset + // value of the monotonic counter + // (this may be easier to implement with `AtomicU8.fetch_sub` but that API is not + // available on ARMv6-M) + + // this core will reset the monotonic counter + const FIRST: u8 = 0; + + if core == FIRST { + for &i in analysis.timer_queues.keys() { + let rv = util::rendezvous_ident(i); + + const_app.push(quote!( + #[rtfm::export::shared] + static #rv: rtfm::export::Barrier = rtfm::export::Barrier::new(); + )); + + // wait until all the other cores have reached the RV point + if i != FIRST { + stmts.push(quote!( + #rv.wait(); + )); + } + } + + let rv = util::rendezvous_ident(core); + stmts.push(quote!( + // the compiler fences are used to prevent `reset` from being re-ordering wrt to + // the atomic operations -- we don't know if `reset` contains load or store + // operations + + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); + + // reset the counter + <#m as rtfm::Monotonic>::reset(); + + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); + + // now unblock all the other cores + #rv.release(); + )); + } else { + let rv = util::rendezvous_ident(core); + + // let the first core know that we have reached the RV point + stmts.push(quote!( + #rv.release(); + )); + + let rv = util::rendezvous_ident(FIRST); + + // wait until the first core has reset the monotonic timer + stmts.push(quote!( + #rv.wait(); + )); + } + } + } + + // enable the interrupts -- this completes the `init`-ialization phase + stmts.push(quote!(rtfm::export::interrupt::enable();)); + + (const_app, stmts) +} diff --git a/macros/src/codegen/pre_init.rs b/macros/src/codegen/pre_init.rs new file mode 100644 index 0000000..3ba17dc --- /dev/null +++ b/macros/src/codegen/pre_init.rs @@ -0,0 +1,150 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::ast::{App, HardwareTaskKind}; + +use crate::{analyze::Analysis, check::Extra, codegen::util}; + +/// Generates code that runs before `#[init]` +pub fn codegen( + core: u8, + app: &App, + analysis: &Analysis, + extra: &Extra, +) -> ( + // `const_app_pre_init` -- `static` variables for barriers + Vec, + // `pre_init_stmts` + Vec, +) { + let mut const_app = vec![]; + let mut stmts = vec![]; + + // disable interrupts -- `init` must run with interrupts disabled + stmts.push(quote!(rtfm::export::interrupt::disable();)); + + // populate this core `FreeQueue`s + for (name, senders) in &analysis.free_queues { + let task = &app.software_tasks[name]; + let cap = task.args.capacity; + + for &sender in senders.keys() { + if sender == core { + let fq = util::fq_ident(name, sender); + + stmts.push(quote!( + (0..#cap).for_each(|i| #fq.enqueue_unchecked(i)); + )); + } + } + } + + stmts.push(quote!( + let mut core = rtfm::export::Peripherals::steal(); + )); + + let device = extra.device; + let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS); + + // unmask interrupts and set their priorities + for (&priority, name) in analysis + .interrupts + .get(&core) + .iter() + .flat_map(|interrupts| *interrupts) + .chain(app.hardware_tasks.iter().flat_map(|(name, task)| { + if task.kind == HardwareTaskKind::Interrupt { + Some((&task.args.priority, task.args.binds(name))) + } else { + // we do exceptions in another pass + None + } + })) + { + // compile time assert that this priority is supported by the device + stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];)); + + // NOTE this also checks that the interrupt exists in the `Interrupt` enumeration + stmts.push(quote!( + core.NVIC.set_priority( + #device::Interrupt::#name, + rtfm::export::logical2hw(#priority, #nvic_prio_bits), + ); + )); + + // NOTE unmask the interrupt *after* setting its priority: changing the priority of a pended + // interrupt is implementation defined + stmts.push(quote!(core.NVIC.enable(#device::Interrupt::#name);)); + } + + // cross-spawn barriers: now that priorities have been set and the interrupts have been unmasked + // we are ready to receive messages from *other* cores + if analysis.spawn_barriers.contains_key(&core) { + let sb = util::spawn_barrier(core); + + const_app.push(quote!( + #[rtfm::export::shared] + static #sb: rtfm::export::Barrier = rtfm::export::Barrier::new(); + )); + + // unblock cores that may send us a message + stmts.push(quote!( + #sb.release(); + )); + } + + // set exception priorities + for (name, priority) in app.hardware_tasks.iter().filter_map(|(name, task)| { + if task.kind == HardwareTaskKind::Exception { + Some((task.args.binds(name), task.args.priority)) + } else { + None + } + }) { + // compile time assert that this priority is supported by the device + stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];)); + + stmts.push(quote!(core.SCB.set_priority( + rtfm::export::SystemHandler::#name, + rtfm::export::logical2hw(#priority, #nvic_prio_bits), + );)); + } + + // initialize the SysTick + if let Some(tq) = analysis.timer_queues.get(&core) { + let priority = tq.priority; + + // compile time assert that this priority is supported by the device + stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];)); + + stmts.push(quote!(core.SCB.set_priority( + rtfm::export::SystemHandler::SysTick, + rtfm::export::logical2hw(#priority, #nvic_prio_bits), + );)); + + stmts.push(quote!( + core.SYST.set_clock_source(rtfm::export::SystClkSource::Core); + core.SYST.enable_counter(); + core.DCB.enable_trace(); + )); + } + + // if there's no user `#[idle]` then optimize returning from interrupt handlers + if app.idles.get(&core).is_none() { + // Set SLEEPONEXIT bit to enter sleep mode when returning from ISR + stmts.push(quote!(core.SCB.scr.modify(|r| r | 1 << 1);)); + } + + // cross-spawn barriers: wait until other cores are ready to receive messages + for (&receiver, senders) in &analysis.spawn_barriers { + // only block here if `init` can send messages to `receiver` + if senders.get(&core) == Some(&true) { + let sb = util::spawn_barrier(receiver); + + stmts.push(quote!( + #sb.wait(); + )); + } + } + + (const_app, stmts) +} diff --git a/macros/src/codegen/resources.rs b/macros/src/codegen/resources.rs new file mode 100644 index 0000000..2dd10ea --- /dev/null +++ b/macros/src/codegen/resources.rs @@ -0,0 +1,115 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::{ + analyze::{Location, Ownership}, + ast::App, +}; + +use crate::{analyze::Analysis, check::Extra, codegen::util}; + +/// Generates `static [mut]` variables and resource proxies +pub fn codegen( + app: &App, + analysis: &Analysis, + extra: &Extra, +) -> ( + // const_app -- the `static [mut]` variables behind the proxies + Vec, + // mod_resources -- the `resources` module + TokenStream2, +) { + let mut const_app = vec![]; + let mut mod_resources = vec![]; + + for (name, res, expr, loc) in app.resources(analysis) { + let cfgs = &res.cfgs; + let ty = &res.ty; + + { + let loc_attr = match loc { + Location::Owned { + core, + cross_initialized: false, + } => util::cfg_core(*core, app.args.cores), + + // shared `static`s and cross-initialized resources need to be in `.shared` memory + _ => Some(quote!(#[rtfm::export::shared])), + }; + + let (ty, expr) = if let Some(expr) = expr { + (quote!(#ty), quote!(#expr)) + } else { + ( + quote!(core::mem::MaybeUninit<#ty>), + quote!(core::mem::MaybeUninit::uninit()), + ) + }; + + let attrs = &res.attrs; + const_app.push(quote!( + #loc_attr + #(#attrs)* + #(#cfgs)* + static mut #name: #ty = #expr; + )); + } + + // generate a resource proxy if needed + if res.mutability.is_some() { + if let Some(Ownership::Shared { ceiling }) = analysis.ownerships.get(name) { + let cfg_core = util::cfg_core(loc.core().expect("UNREACHABLE"), app.args.cores); + + mod_resources.push(quote!( + #(#cfgs)* + #cfg_core + pub struct #name<'a> { + priority: &'a Priority, + } + + #(#cfgs)* + #cfg_core + impl<'a> #name<'a> { + #[inline(always)] + pub unsafe fn new(priority: &'a Priority) -> Self { + #name { priority } + } + + #[inline(always)] + pub unsafe fn priority(&self) -> &Priority { + self.priority + } + } + )); + + let ptr = if expr.is_none() { + quote!(#name.as_mut_ptr()) + } else { + quote!(&mut #name) + }; + + const_app.push(util::impl_mutex( + extra, + cfgs, + cfg_core.as_ref(), + true, + name, + quote!(#ty), + *ceiling, + ptr, + )); + } + } + } + + let mod_resources = if mod_resources.is_empty() { + quote!() + } else { + quote!(mod resources { + use rtfm::export::Priority; + + #(#mod_resources)* + }) + }; + + (const_app, mod_resources) +} diff --git a/macros/src/codegen/resources_struct.rs b/macros/src/codegen/resources_struct.rs new file mode 100644 index 0000000..0248f19 --- /dev/null +++ b/macros/src/codegen/resources_struct.rs @@ -0,0 +1,178 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::{ast::App, Context}; + +use crate::{analyze::Analysis, codegen::util}; + +pub fn codegen( + ctxt: Context, + priority: u8, + needs_lt: &mut bool, + app: &App, + analysis: &Analysis, +) -> (TokenStream2, TokenStream2) { + let mut lt = None; + + let resources = match ctxt { + Context::Init(core) => &app.inits[&core].args.resources, + Context::Idle(core) => &app.idles[&core].args.resources, + Context::HardwareTask(name) => &app.hardware_tasks[name].args.resources, + Context::SoftwareTask(name) => &app.software_tasks[name].args.resources, + }; + + let mut fields = vec![]; + let mut values = vec![]; + let mut has_cfgs = false; + + for name in resources { + let (res, expr) = app.resource(name).expect("UNREACHABLE"); + + let cfgs = &res.cfgs; + has_cfgs |= !cfgs.is_empty(); + + let mut_ = res.mutability; + let ty = &res.ty; + + if ctxt.is_init() { + if !analysis.ownerships.contains_key(name) { + // owned by `init` + fields.push(quote!( + #(#cfgs)* + pub #name: &'static #mut_ #ty + )); + + values.push(quote!( + #(#cfgs)* + #name: &#mut_ #name + )); + } else { + // owned by someone else + lt = Some(quote!('a)); + + fields.push(quote!( + #(#cfgs)* + pub #name: &'a mut #ty + )); + + values.push(quote!( + #(#cfgs)* + #name: &mut #name + )); + } + } else { + let ownership = &analysis.ownerships[name]; + + if ownership.needs_lock(priority) { + if mut_.is_none() { + lt = Some(quote!('a)); + + fields.push(quote!( + #(#cfgs)* + pub #name: &'a #ty + )); + } else { + // resource proxy + lt = Some(quote!('a)); + + fields.push(quote!( + #(#cfgs)* + pub #name: resources::#name<'a> + )); + + values.push(quote!( + #(#cfgs)* + #name: resources::#name::new(priority) + + )); + + continue; + } + } else { + let lt = if ctxt.runs_once() { + quote!('static) + } else { + lt = Some(quote!('a)); + quote!('a) + }; + + if ownership.is_owned() || mut_.is_none() { + fields.push(quote!( + #(#cfgs)* + pub #name: &#lt #mut_ #ty + )); + } else { + fields.push(quote!( + #(#cfgs)* + pub #name: &#lt mut #ty + )); + } + } + + let is_late = expr.is_none(); + if is_late { + let expr = if mut_.is_some() { + quote!(&mut *#name.as_mut_ptr()) + } else { + quote!(&*#name.as_ptr()) + }; + + values.push(quote!( + #(#cfgs)* + #name: #expr + )); + } else { + values.push(quote!( + #(#cfgs)* + #name: &#mut_ #name + )); + } + } + } + + if lt.is_some() { + *needs_lt = true; + + // the struct could end up empty due to `cfg`s leading to an error due to `'a` being unused + if has_cfgs { + fields.push(quote!( + #[doc(hidden)] + pub __marker__: core::marker::PhantomData<&'a ()> + )); + + values.push(quote!(__marker__: core::marker::PhantomData)) + } + } + + let core = ctxt.core(app); + let cores = app.args.cores; + let cfg_core = util::cfg_core(core, cores); + let doc = format!("Resources `{}` has access to", ctxt.ident(app)); + let ident = util::resources_ident(ctxt, app); + let item = quote!( + #cfg_core + #[allow(non_snake_case)] + #[doc = #doc] + pub struct #ident<#lt> { + #(#fields,)* + } + ); + + let arg = if ctxt.is_init() { + None + } else { + Some(quote!(priority: &#lt rtfm::export::Priority)) + }; + let constructor = quote!( + #cfg_core + impl<#lt> #ident<#lt> { + #[inline(always)] + unsafe fn new(#arg) -> Self { + #ident { + #(#values,)* + } + } + } + ); + + (item, constructor) +} diff --git a/macros/src/codegen/schedule.rs b/macros/src/codegen/schedule.rs new file mode 100644 index 0000000..57f01a2 --- /dev/null +++ b/macros/src/codegen/schedule.rs @@ -0,0 +1,95 @@ +use std::collections::{BTreeMap, HashSet}; + +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::ast::App; + +use crate::{ + check::Extra, + codegen::{schedule_body, util}, +}; + +/// Generates all `${ctxt}::Schedule` methods +pub fn codegen(app: &App, extra: &Extra) -> Vec { + let mut items = vec![]; + + let mut seen = BTreeMap::>::new(); + for (scheduler, schedulees) in app.schedule_callers() { + let m = extra.monotonic(); + let instant = quote!(<#m as rtfm::Monotonic>::Instant); + + let sender = scheduler.core(app); + let cfg_sender = util::cfg_core(sender, app.args.cores); + let seen = seen.entry(sender).or_default(); + let mut methods = vec![]; + + for name in schedulees { + let schedulee = &app.software_tasks[name]; + let cfgs = &schedulee.cfgs; + let (args, _, untupled, ty) = util::regroup_inputs(&schedulee.inputs); + let args = &args; + + if scheduler.is_init() { + // `init` uses a special `schedule` implementation; it doesn't use the + // `schedule_${name}` functions which are shared by other contexts + + let body = schedule_body::codegen(scheduler, &name, app); + + methods.push(quote!( + #(#cfgs)* + fn #name(&self, instant: #instant #(,#args)*) -> Result<(), #ty> { + #body + } + )); + } else { + let schedule = util::schedule_ident(name, sender); + + if !seen.contains(name) { + // generate a `schedule_${name}_S${sender}` function + seen.insert(name); + + let body = schedule_body::codegen(scheduler, &name, app); + + items.push(quote!( + #cfg_sender + #(#cfgs)* + unsafe fn #schedule( + priority: &rtfm::export::Priority, + instant: #instant + #(,#args)* + ) -> Result<(), #ty> { + #body + } + )); + } + + methods.push(quote!( + #(#cfgs)* + #[inline(always)] + fn #name(&self, instant: #instant #(,#args)*) -> Result<(), #ty> { + unsafe { + #schedule(self.priority(), instant #(,#untupled)*) + } + } + )); + } + } + + let lt = if scheduler.is_init() { + None + } else { + Some(quote!('a)) + }; + + let scheduler = scheduler.ident(app); + debug_assert!(!methods.is_empty()); + items.push(quote!( + #cfg_sender + impl<#lt> #scheduler::Schedule<#lt> { + #(#methods)* + } + )); + } + + items +} diff --git a/macros/src/codegen/schedule_body.rs b/macros/src/codegen/schedule_body.rs new file mode 100644 index 0000000..208fd0b --- /dev/null +++ b/macros/src/codegen/schedule_body.rs @@ -0,0 +1,61 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::{ast::App, Context}; +use syn::Ident; + +use crate::codegen::util; + +pub fn codegen(scheduler: Context, name: &Ident, app: &App) -> TokenStream2 { + let sender = scheduler.core(app); + let schedulee = &app.software_tasks[name]; + let receiver = schedulee.args.core; + + let fq = util::fq_ident(name, sender); + let tq = util::tq_ident(sender); + let (dequeue, enqueue) = if scheduler.is_init() { + (quote!(#fq.dequeue()), quote!(#tq.enqueue_unchecked(nr);)) + } else { + ( + quote!((#fq { priority }).lock(|fq| fq.split().1.dequeue())), + quote!((#tq { priority }).lock(|tq| tq.enqueue_unchecked(nr));), + ) + }; + + let write_instant = if app.uses_schedule(receiver) { + let instants = util::instants_ident(name, sender); + + Some(quote!( + #instants.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(instant); + )) + } else { + None + }; + + let (_, tupled, _, _) = util::regroup_inputs(&schedulee.inputs); + let inputs = util::inputs_ident(name, sender); + let t = util::schedule_t_ident(sender); + quote!( + unsafe { + use rtfm::Mutex as _; + + let input = #tupled; + if let Some(index) = #dequeue { + #inputs.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(input); + + #write_instant + + let nr = rtfm::export::NotReady { + instant, + index, + task: #t::#name, + }; + + #enqueue + + Ok(()) + } else { + Err(input) + } + } + ) +} diff --git a/macros/src/codegen/software_tasks.rs b/macros/src/codegen/software_tasks.rs new file mode 100644 index 0000000..8b2c0cd --- /dev/null +++ b/macros/src/codegen/software_tasks.rs @@ -0,0 +1,169 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::{ast::App, Context}; + +use crate::{ + analyze::Analysis, + check::Extra, + codegen::{locals, module, resources_struct, util}, +}; + +pub fn codegen( + app: &App, + analysis: &Analysis, + extra: &Extra, +) -> ( + // const_app_software_tasks -- free queues, buffers and `${task}Resources` constructors + Vec, + // root_software_tasks -- items that must be placed in the root of the crate: + // - `${task}Locals` structs + // - `${task}Resources` structs + // - `${task}` modules + Vec, + // user_software_tasks -- the `#[task]` functions written by the user + Vec, +) { + let mut const_app = vec![]; + let mut root = vec![]; + let mut user_tasks = vec![]; + + for (name, task) in &app.software_tasks { + let receiver = task.args.core; + + let inputs = &task.inputs; + let (_, _, _, input_ty) = util::regroup_inputs(inputs); + + let cap = task.args.capacity; + let cap_lit = util::capacity_literal(cap); + let cap_ty = util::capacity_typenum(cap, true); + + // create free queues and inputs / instants buffers + if let Some(free_queues) = analysis.free_queues.get(name) { + for (&sender, &ceiling) in free_queues { + let cfg_sender = util::cfg_core(sender, app.args.cores); + let fq = util::fq_ident(name, sender); + + let (loc, fq_ty, fq_expr) = if receiver == sender { + ( + cfg_sender.clone(), + quote!(rtfm::export::SCFQ<#cap_ty>), + quote!(rtfm::export::Queue(unsafe { + rtfm::export::iQueue::u8_sc() + })), + ) + } else { + ( + Some(quote!(#[rtfm::export::shared])), + quote!(rtfm::export::MCFQ<#cap_ty>), + quote!(rtfm::export::Queue(rtfm::export::iQueue::u8())), + ) + }; + let loc = &loc; + + const_app.push(quote!( + /// Queue version of a free-list that keeps track of empty slots in + /// the following buffers + #loc + static mut #fq: #fq_ty = #fq_expr; + )); + + // Generate a resource proxy if needed + if let Some(ceiling) = ceiling { + const_app.push(quote!( + #cfg_sender + struct #fq<'a> { + priority: &'a rtfm::export::Priority, + } + )); + + const_app.push(util::impl_mutex( + extra, + &[], + cfg_sender.as_ref(), + false, + &fq, + fq_ty, + ceiling, + quote!(&mut #fq), + )); + } + + let ref elems = (0..cap) + .map(|_| quote!(core::mem::MaybeUninit::uninit())) + .collect::>(); + + if app.uses_schedule(receiver) { + let m = extra.monotonic(); + let instants = util::instants_ident(name, sender); + + const_app.push(quote!( + #loc + /// Buffer that holds the instants associated to the inputs of a task + static mut #instants: + [core::mem::MaybeUninit<<#m as rtfm::Monotonic>::Instant>; #cap_lit] = + [#(#elems,)*]; + )); + } + + let inputs = util::inputs_ident(name, sender); + const_app.push(quote!( + #loc + /// Buffer that holds the inputs of a task + static mut #inputs: [core::mem::MaybeUninit<#input_ty>; #cap_lit] = + [#(#elems,)*]; + )); + } + } + + // `${task}Resources` + let mut needs_lt = false; + if !task.args.resources.is_empty() { + let (item, constructor) = resources_struct::codegen( + Context::SoftwareTask(name), + task.args.priority, + &mut needs_lt, + app, + analysis, + ); + + root.push(item); + + const_app.push(constructor); + } + + // `${task}Locals` + let mut locals_pat = None; + if !task.locals.is_empty() { + let (struct_, pat) = locals::codegen(Context::SoftwareTask(name), &task.locals, app); + + locals_pat = Some(pat); + root.push(struct_); + } + + let cfg_receiver = util::cfg_core(receiver, app.args.cores); + let context = &task.context; + let attrs = &task.attrs; + let cfgs = &task.cfgs; + let stmts = &task.stmts; + user_tasks.push(quote!( + #(#attrs)* + #(#cfgs)* + #cfg_receiver + #[allow(non_snake_case)] + fn #name(#(#locals_pat,)* #context: #name::Context #(,#inputs)*) { + use rtfm::Mutex as _; + + #(#stmts)* + } + )); + + root.push(module::codegen( + Context::SoftwareTask(name), + needs_lt, + app, + extra, + )); + } + + (const_app, root, user_tasks) +} diff --git a/macros/src/codegen/spawn.rs b/macros/src/codegen/spawn.rs new file mode 100644 index 0000000..1539e27 --- /dev/null +++ b/macros/src/codegen/spawn.rs @@ -0,0 +1,127 @@ +use std::collections::{BTreeMap, HashSet}; + +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::ast::App; + +use crate::{ + analyze::Analysis, + check::Extra, + codegen::{spawn_body, util}, +}; + +/// Generates all `${ctxt}::Spawn` methods +pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec { + let mut items = vec![]; + + let mut seen = BTreeMap::>::new(); + for (spawner, spawnees) in app.spawn_callers() { + let sender = spawner.core(app); + let cfg_sender = util::cfg_core(sender, app.args.cores); + let seen = seen.entry(sender).or_default(); + let mut methods = vec![]; + + for name in spawnees { + let spawnee = &app.software_tasks[name]; + let receiver = spawnee.args.core; + let cfgs = &spawnee.cfgs; + let (args, _, untupled, ty) = util::regroup_inputs(&spawnee.inputs); + let args = &args; + + if spawner.is_init() { + // `init` uses a special spawn implementation; it doesn't use the `spawn_${name}` + // functions which are shared by other contexts + + let body = spawn_body::codegen(spawner, &name, app, analysis, extra); + + let let_instant = if app.uses_schedule(receiver) { + let m = extra.monotonic(); + + Some(quote!(let instant = unsafe { <#m as rtfm::Monotonic>::zero() };)) + } else { + None + }; + + methods.push(quote!( + #(#cfgs)* + fn #name(&self #(,#args)*) -> Result<(), #ty> { + #let_instant + #body + } + )); + } else { + let spawn = util::spawn_ident(name, sender); + + if !seen.contains(name) { + // generate a `spawn_${name}_S${sender}` function + seen.insert(name); + + let instant = if app.uses_schedule(receiver) { + let m = extra.monotonic(); + + Some(quote!(, instant: <#m as rtfm::Monotonic>::Instant)) + } else { + None + }; + + let body = spawn_body::codegen(spawner, &name, app, analysis, extra); + + items.push(quote!( + #cfg_sender + #(#cfgs)* + unsafe fn #spawn( + priority: &rtfm::export::Priority + #instant + #(,#args)* + ) -> Result<(), #ty> { + #body + } + )); + } + + let (let_instant, instant) = if app.uses_schedule(receiver) { + let m = extra.monotonic(); + + ( + Some(if spawner.is_idle() { + quote!(let instant = <#m as rtfm::Monotonic>::now();) + } else { + quote!(let instant = self.instant();) + }), + Some(quote!(, instant)), + ) + } else { + (None, None) + }; + + methods.push(quote!( + #(#cfgs)* + #[inline(always)] + fn #name(&self #(,#args)*) -> Result<(), #ty> { + unsafe { + #let_instant + #spawn(self.priority() #instant #(,#untupled)*) + } + } + )); + } + } + + let lt = if spawner.is_init() { + None + } else { + Some(quote!('a)) + }; + + let spawner = spawner.ident(app); + debug_assert!(!methods.is_empty()); + items.push(quote!( + #cfg_sender + impl<#lt> #spawner::Spawn<#lt> { + #(#methods)* + } + )); + } + + items +} diff --git a/macros/src/codegen/spawn_body.rs b/macros/src/codegen/spawn_body.rs new file mode 100644 index 0000000..83cb5c0 --- /dev/null +++ b/macros/src/codegen/spawn_body.rs @@ -0,0 +1,81 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::{ast::App, Context}; +use syn::Ident; + +use crate::{analyze::Analysis, check::Extra, codegen::util}; + +pub fn codegen( + spawner: Context, + name: &Ident, + app: &App, + analysis: &Analysis, + extra: &Extra, +) -> TokenStream2 { + let sender = spawner.core(app); + let spawnee = &app.software_tasks[name]; + let priority = spawnee.args.priority; + let receiver = spawnee.args.core; + + let write_instant = if app.uses_schedule(receiver) { + let instants = util::instants_ident(name, sender); + + Some(quote!( + #instants.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(instant); + )) + } else { + None + }; + + let t = util::spawn_t_ident(receiver, priority, sender); + let fq = util::fq_ident(name, sender); + let rq = util::rq_ident(receiver, priority, sender); + let (dequeue, enqueue) = if spawner.is_init() { + ( + quote!(#fq.dequeue()), + quote!(#rq.enqueue_unchecked((#t::#name, index));), + ) + } else { + ( + quote!((#fq { priority }.lock(|fq| fq.split().1.dequeue()))), + quote!((#rq { priority }.lock(|rq| { + rq.split().0.enqueue_unchecked((#t::#name, index)) + }));), + ) + }; + + let device = extra.device; + let interrupt = &analysis.interrupts[&receiver][&priority]; + let pend = if sender != receiver { + quote!( + #device::xpend(#receiver, #device::Interrupt::#interrupt); + ) + } else { + quote!( + rtfm::pend(#device::Interrupt::#interrupt); + ) + }; + + let (_, tupled, _, _) = util::regroup_inputs(&spawnee.inputs); + let inputs = util::inputs_ident(name, sender); + quote!( + unsafe { + use rtfm::Mutex as _; + + let input = #tupled; + if let Some(index) = #dequeue { + #inputs.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(input); + + #write_instant + + #enqueue + + #pend + + Ok(()) + } else { + Err(input) + } + } + ) +} diff --git a/macros/src/codegen/timer_queue.rs b/macros/src/codegen/timer_queue.rs new file mode 100644 index 0000000..cb84577 --- /dev/null +++ b/macros/src/codegen/timer_queue.rs @@ -0,0 +1,147 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::ast::App; + +use crate::{analyze::Analysis, check::Extra, codegen::util}; + +/// Generates timer queues and timer queue handlers +pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec { + let mut items = vec![]; + + for (&sender, timer_queue) in &analysis.timer_queues { + let cfg_sender = util::cfg_core(sender, app.args.cores); + let t = util::schedule_t_ident(sender); + + // Enumeration of `schedule`-able tasks + { + let variants = timer_queue + .tasks + .iter() + .map(|name| { + let cfgs = &app.software_tasks[name].cfgs; + + quote!( + #(#cfgs)* + #name + ) + }) + .collect::>(); + + let doc = format!("Tasks that can be scheduled from core #{}", sender); + items.push(quote!( + #cfg_sender + #[doc = #doc] + #[allow(non_camel_case_types)] + #[derive(Clone, Copy)] + enum #t { + #(#variants,)* + } + )); + } + + let tq = util::tq_ident(sender); + + // Static variable and resource proxy + { + let doc = format!("Core #{} timer queue", sender); + let m = extra.monotonic(); + let n = util::capacity_typenum(timer_queue.capacity, false); + let tq_ty = quote!(rtfm::export::TimerQueue<#m, #t, #n>); + + items.push(quote!( + #cfg_sender + #[doc = #doc] + static mut #tq: #tq_ty = rtfm::export::TimerQueue( + rtfm::export::BinaryHeap( + rtfm::export::iBinaryHeap::new() + ) + ); + + #cfg_sender + struct #tq<'a> { + priority: &'a rtfm::export::Priority, + } + )); + + items.push(util::impl_mutex( + extra, + &[], + cfg_sender.as_ref(), + false, + &tq, + tq_ty, + timer_queue.ceiling, + quote!(&mut #tq), + )); + } + + // Timer queue handler + { + let device = extra.device; + let arms = timer_queue + .tasks + .iter() + .map(|name| { + let task = &app.software_tasks[name]; + + let cfgs = &task.cfgs; + let priority = task.args.priority; + let receiver = task.args.core; + let rq = util::rq_ident(receiver, priority, sender); + let rqt = util::spawn_t_ident(receiver, priority, sender); + let interrupt = &analysis.interrupts[&receiver][&priority]; + + let pend = if sender != receiver { + quote!( + #device::xpend(#receiver, #device::Interrupt::#interrupt); + ) + } else { + quote!( + rtfm::pend(#device::Interrupt::#interrupt); + ) + }; + + quote!( + #(#cfgs)* + #t::#name => { + (#rq { priority: &rtfm::export::Priority::new(PRIORITY) }).lock(|rq| { + rq.split().0.enqueue_unchecked((#rqt::#name, index)) + }); + + #pend + } + ) + }) + .collect::>(); + + let priority = timer_queue.priority; + items.push(quote!( + #cfg_sender + #[no_mangle] + unsafe fn SysTick() { + use rtfm::Mutex as _; + + /// The priority of this handler + const PRIORITY: u8 = #priority; + + rtfm::export::run(PRIORITY, || { + while let Some((task, index)) = (#tq { + // NOTE dynamic priority is always the static priority at this point + priority: &rtfm::export::Priority::new(PRIORITY), + }) + // NOTE `inline(always)` produces faster and smaller code + .lock(#[inline(always)] + |tq| tq.dequeue()) + { + match task { + #(#arms)* + } + } + }); + } + )); + } + } + + items +} diff --git a/macros/src/codegen/util.rs b/macros/src/codegen/util.rs new file mode 100644 index 0000000..203fcee --- /dev/null +++ b/macros/src/codegen/util.rs @@ -0,0 +1,253 @@ +use proc_macro2::{Span, TokenStream as TokenStream2}; +use quote::quote; +use rtfm_syntax::{ast::App, Context, Core}; +use syn::{ArgCaptured, Attribute, Ident, IntSuffix, LitInt}; + +use crate::check::Extra; + +/// Turns `capacity` into an unsuffixed integer literal +pub fn capacity_literal(capacity: u8) -> LitInt { + LitInt::new(u64::from(capacity), IntSuffix::None, Span::call_site()) +} + +/// Turns `capacity` into a type-level (`typenum`) integer +pub fn capacity_typenum(capacity: u8, round_up_to_power_of_two: bool) -> TokenStream2 { + let capacity = if round_up_to_power_of_two { + capacity.checked_next_power_of_two().expect("UNREACHABLE") + } else { + capacity + }; + + let ident = Ident::new(&format!("U{}", capacity), Span::call_site()); + + quote!(rtfm::export::consts::#ident) +} + +/// Generates a `#[cfg(core = "0")]` attribute if we are in multi-core mode +pub fn cfg_core(core: Core, cores: u8) -> Option { + if cores == 1 { + None + } else { + let core = core.to_string(); + Some(quote!(#[cfg(core = #core)])) + } +} + +/// Identifier for the free queue +/// +/// There may be more than one free queue per task because we need one for each sender core so we +/// include the sender (e.g. `S0`) in the name +pub fn fq_ident(task: &Ident, sender: Core) -> Ident { + Ident::new( + &format!("{}_S{}_FQ", task.to_string(), sender), + Span::call_site(), + ) +} + +/// Generates a `Mutex` implementation +pub fn impl_mutex( + extra: &Extra, + cfgs: &[Attribute], + cfg_core: Option<&TokenStream2>, + resources_prefix: bool, + name: &Ident, + ty: TokenStream2, + ceiling: u8, + ptr: TokenStream2, +) -> TokenStream2 { + let (path, priority) = if resources_prefix { + (quote!(resources::#name), quote!(self.priority())) + } else { + (quote!(#name), quote!(self.priority)) + }; + + let device = extra.device; + quote!( + #(#cfgs)* + #cfg_core + impl<'a> rtfm::Mutex for #path<'a> { + type T = #ty; + + #[inline(always)] + fn lock(&mut self, f: impl FnOnce(&mut #ty) -> R) -> R { + /// Priority ceiling + const CEILING: u8 = #ceiling; + + unsafe { + rtfm::export::lock( + #ptr, + #priority, + CEILING, + #device::NVIC_PRIO_BITS, + f, + ) + } + } + } + ) +} + +/// Generates an identifier for a cross-initialization barrier +pub fn init_barrier(initializer: Core) -> Ident { + Ident::new(&format!("IB{}", initializer), Span::call_site()) +} + +/// Generates an identifier for the `INPUTS` buffer (`spawn` & `schedule` API) +pub fn inputs_ident(task: &Ident, sender: Core) -> Ident { + Ident::new(&format!("{}_S{}_INPUTS", task, sender), Span::call_site()) +} + +/// Generates an identifier for the `INSTANTS` buffer (`schedule` API) +pub fn instants_ident(task: &Ident, sender: Core) -> Ident { + Ident::new(&format!("{}_S{}_INSTANTS", task, sender), Span::call_site()) +} + +/// Generates a pre-reexport identifier for the "late resources" struct +pub fn late_resources_ident(init: &Ident) -> Ident { + Ident::new( + &format!("{}LateResources", init.to_string()), + Span::call_site(), + ) +} + +/// Generates a pre-reexport identifier for the "locals" struct +pub fn locals_ident(ctxt: Context, app: &App) -> Ident { + let mut s = match ctxt { + Context::Init(core) => app.inits[&core].name.to_string(), + Context::Idle(core) => app.idles[&core].name.to_string(), + Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(), + }; + + s.push_str("Locals"); + + Ident::new(&s, Span::call_site()) +} + +/// Generates an identifier for a rendezvous barrier +pub fn rendezvous_ident(core: Core) -> Ident { + Ident::new(&format!("RV{}", core), Span::call_site()) +} + +// Regroups the inputs of a task +// +// `inputs` could be &[`input: Foo`] OR &[`mut x: i32`, `ref y: i64`] +pub fn regroup_inputs( + inputs: &[ArgCaptured], +) -> ( + // args e.g. &[`_0`], &[`_0: i32`, `_1: i64`] + Vec, + // tupled e.g. `_0`, `(_0, _1)` + TokenStream2, + // untupled e.g. &[`_0`], &[`_0`, `_1`] + Vec, + // ty e.g. `Foo`, `(i32, i64)` + TokenStream2, +) { + if inputs.len() == 1 { + let ty = &inputs[0].ty; + + ( + vec![quote!(_0: #ty)], + quote!(_0), + vec![quote!(_0)], + quote!(#ty), + ) + } else { + let mut args = vec![]; + let mut pats = vec![]; + let mut tys = vec![]; + + for (i, input) in inputs.iter().enumerate() { + let i = Ident::new(&format!("_{}", i), Span::call_site()); + let ty = &input.ty; + + args.push(quote!(#i: #ty)); + + pats.push(quote!(#i)); + + tys.push(quote!(#ty)); + } + + let tupled = { + let pats = pats.clone(); + quote!((#(#pats,)*)) + }; + let ty = quote!((#(#tys,)*)); + (args, tupled, pats, ty) + } +} + +/// Generates a pre-reexport identifier for the "resources" struct +pub fn resources_ident(ctxt: Context, app: &App) -> Ident { + let mut s = match ctxt { + Context::Init(core) => app.inits[&core].name.to_string(), + Context::Idle(core) => app.idles[&core].name.to_string(), + Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(), + }; + + s.push_str("Resources"); + + Ident::new(&s, Span::call_site()) +} + +/// Generates an identifier for a ready queue +/// +/// Each core may have several task dispatchers, one for each priority level. Each task dispatcher +/// in turn may use more than one ready queue because the queues are SPSC queues so one is needed +/// per sender core. +pub fn rq_ident(receiver: Core, priority: u8, sender: Core) -> Ident { + Ident::new( + &format!("R{}_P{}_S{}_RQ", receiver, priority, sender), + Span::call_site(), + ) +} + +/// Generates an identifier for a "schedule" function +/// +/// The methods of the `Schedule` structs invoke these functions. As one task may be `schedule`-ed +/// by different cores we need one "schedule" function per possible task-sender pair +pub fn schedule_ident(name: &Ident, sender: Core) -> Ident { + Ident::new( + &format!("schedule_{}_S{}", name.to_string(), sender), + Span::call_site(), + ) +} + +/// Generates an identifier for the `enum` of `schedule`-able tasks +pub fn schedule_t_ident(core: Core) -> Ident { + Ident::new(&format!("T{}", core), Span::call_site()) +} + +/// Generates an identifier for a cross-spawn barrier +pub fn spawn_barrier(receiver: Core) -> Ident { + Ident::new(&format!("SB{}", receiver), Span::call_site()) +} + +/// Generates an identifier for a "spawn" function +/// +/// The methods of the `Spawn` structs invoke these functions. As one task may be `spawn`-ed by +/// different cores we need one "spawn" function per possible task-sender pair +pub fn spawn_ident(name: &Ident, sender: Core) -> Ident { + Ident::new( + &format!("spawn_{}_S{}", name.to_string(), sender), + Span::call_site(), + ) +} + +/// Generates an identifier for the `enum` of `spawn`-able tasks +/// +/// This identifier needs the same structure as the `RQ` identifier because there's one ready queue +/// for each of these `T` enums +pub fn spawn_t_ident(receiver: Core, priority: u8, sender: Core) -> Ident { + Ident::new( + &format!("R{}_P{}_S{}_T", receiver, priority, sender), + Span::call_site(), + ) +} + +/// Generates an identifier for a timer queue +/// +/// At most there's one timer queue per core +pub fn tq_ident(core: Core) -> Ident { + Ident::new(&format!("TQ{}", core), Span::call_site()) +} diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 736289c..6e1a797 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -6,307 +6,41 @@ extern crate proc_macro; use proc_macro::TokenStream; use std::{fs, path::Path}; -use syn::parse_macro_input; +use rtfm_syntax::Settings; mod analyze; mod check; mod codegen; -mod syntax; +#[cfg(test)] +mod tests; -/// Attribute used to declare a RTFM application -/// -/// This attribute must be applied to a `const` item of type `()`. The `const` item is effectively -/// used as a `mod` item: its value must be a block that contains items commonly found in modules, -/// like functions and `static` variables. -/// -/// The `app` attribute has one mandatory argument: -/// -/// - `device = `. The path must point to a device crate generated using [`svd2rust`] -/// **v0.14.x**. -/// -/// [`svd2rust`]: https://crates.io/crates/svd2rust -/// -/// The items allowed in the block value of the `const` item are specified below: -/// -/// # 1. `static [mut]` variables -/// -/// These variables are used as *resources*. Resources can be owned by tasks or shared between them. -/// Tasks can get `&mut` (exclusives) references to `static mut` resources, but only `&` (shared) -/// references to `static` resources. Lower priority tasks will need a [`lock`] to get a `&mut` -/// reference to a `static mut` resource shared with higher priority tasks. -/// -/// [`lock`]: ../rtfm/trait.Mutex.html#method.lock -/// -/// `static mut` resources that are shared by tasks that run at *different* priorities need to -/// implement the [`Send`] trait. Similarly, `static` resources that are shared by tasks that run at -/// *different* priorities need to implement the [`Sync`] trait. -/// -/// [`Send`]: https://doc.rust-lang.org/core/marker/trait.Send.html -/// [`Sync`]: https://doc.rust-lang.org/core/marker/trait.Sync.html -/// -/// Resources can be initialized at runtime by assigning them `()` (the unit value) as their initial -/// value in their declaration. These "late" resources need to be initialized an the end of the -/// `init` function. -/// -/// The `app` attribute will inject a `resources` module in the root of the crate. This module -/// contains proxy `struct`s that implement the [`Mutex`] trait. The `struct` are named after the -/// `static mut` resources. For example, `static mut FOO: u32 = 0` will map to a `resources::FOO` -/// `struct` that implements the `Mutex` trait. -/// -/// [`Mutex`]: ../rtfm/trait.Mutex.html -/// -/// # 2. `fn` -/// -/// Functions must contain *one* of the following attributes: `init`, `idle`, `interrupt`, -/// `exception` or `task`. The attribute defines the role of the function in the application. -/// -/// ## a. `#[init]` -/// -/// This attribute indicates that the function is to be used as the *initialization function*. There -/// must be exactly one instance of the `init` attribute inside the `app` pseudo-module. The -/// signature of the `init` function must be `[unsafe] fn ()`. -/// -/// The `init` function runs after memory (RAM) is initialized and runs with interrupts disabled. -/// Interrupts are re-enabled after `init` returns. -/// -/// The `init` attribute accepts the following optional arguments: -/// -/// - `resources = [RESOURCE_A, RESOURCE_B, ..]`. This is the list of resources this function has -/// access to. -/// -/// - `schedule = [task_a, task_b, ..]`. This is the list of *software* tasks that this function can -/// schedule to run in the future. *IMPORTANT*: This argument is accepted only if the `timer-queue` -/// feature has been enabled. -/// -/// - `spawn = [task_a, task_b, ..]`. This is the list of *software* tasks that this function can -/// immediately spawn. -/// -/// The `app` attribute will injected a *context* into this function that comprises the following -/// variables: -/// -/// - `core: rtfm::Peripherals`. Exclusive access to core peripherals. See [`rtfm::Peripherals`] for -/// more details. -/// -/// [`rtfm::Peripherals`]: ../rtfm/struct.Peripherals.html -/// -/// - `device: ::Peripherals`. Exclusive access to device-specific peripherals. -/// `` is the path to the device crate declared in the top `app` attribute. -/// -/// - `start: rtfm::Instant`. The `start` time of the system: `Instant(0 /* cycles */)`. **NOTE**: -/// only present if the `timer-queue` feature is enabled. -/// -/// - `resources: _`. An opaque `struct` that contains all the resources assigned to this function. -/// The resource maybe appear by value (`impl Singleton`), by references (`&[mut]`) or by proxy -/// (`impl Mutex`). -/// -/// - `schedule: init::Schedule`. A `struct` that can be used to schedule *software* tasks. -/// **NOTE**: only present if the `timer-queue` feature is enabled. -/// -/// - `spawn: init::Spawn`. A `struct` that can be used to spawn *software* tasks. -/// -/// Other properties / constraints: -/// -/// - The `init` function can **not** be called from software. -/// -/// - The `static mut` variables declared at the beginning of this function will be transformed into -/// `&'static mut` references that are safe to access. For example, `static mut FOO: u32 = 0` will -/// become `FOO: &'static mut u32`. -/// -/// - Assignments (e.g. `FOO = 0`) at the end of this function can be used to initialize *late* -/// resources. -/// -/// ## b. `#[idle]` -/// -/// This attribute indicates that the function is to be used as the *idle task*. There can be at -/// most once instance of the `idle` attribute inside the `app` pseudo-module. The signature of the -/// `idle` function must be `fn() -> !`. -/// -/// The `idle` task is a special task that always runs in the background. The `idle` task runs at -/// the lowest priority of `0`. If the `idle` task is not defined then the runtime sets the -/// [SLEEPONEXIT] bit after executing `init`. -/// -/// [SLEEPONEXIT]: https://developer.arm.com/products/architecture/cpu-architecture/m-profile/docs/100737/0100/power-management/sleep-mode/sleep-on-exit-bit -/// -/// The `idle` attribute accepts the following optional arguments: -/// -/// - `resources = (..)`. Same meaning / function as [`#[init].resources`](#a-init). -/// -/// - `schedule = (..)`. Same meaning / function as [`#[init].schedule`](#a-init). -/// -/// - `spawn = (..)`. Same meaning / function as [`#[init].spawn`](#a-init). -/// -/// The `app` attribute will injected a *context* into this function that comprises the following -/// variables: -/// -/// - `resources: _`. Same meaning / function as [`init.resources`](#a-init). -/// -/// - `schedule: idle::Schedule`. Same meaning / function as [`init.schedule`](#a-init). -/// -/// - `spawn: idle::Spawn`. Same meaning / function as [`init.spawn`](#a-init). -/// -/// Other properties / constraints: -/// -/// - The `idle` function can **not** be called from software. -/// -/// - The `static mut` variables declared at the beginning of this function will be transformed into -/// `&'static mut` references that are safe to access. For example, `static mut FOO: u32 = 0` will -/// become `FOO: &'static mut u32`. -/// -/// ## c. `#[exception]` -/// -/// This attribute indicates that the function is to be used as an *exception handler*, a type of -/// hardware task. The signature of `exception` handlers must be `[unsafe] fn()`. -/// -/// The name of the function must match one of the Cortex-M exceptions that has [configurable -/// priority][system-handler]. -/// -/// [system-handler]: ../cortex_m/peripheral/scb/enum.SystemHandler.html -/// -/// The `exception` attribute accepts the following optional arguments. -/// -/// - `priority = `. This is the static priority of the exception handler. The value must -/// be in the range `1..=(1 << ::NVIC_PRIO_BITS)` where `` is the path to -/// the device crate declared in the top `app` attribute. If this argument is omitted the priority -/// is assumed to be 1. -/// -/// - `resources = (..)`. Same meaning / function as [`#[init].resources`](#a-init). -/// -/// - `schedule = (..)`. Same meaning / function as [`#[init].schedule`](#a-init). -/// -/// - `spawn = (..)`. Same meaning / function as [`#[init].spawn`](#a-init). -/// -/// The `app` attribute will injected a *context* into this function that comprises the following -/// variables: -/// -/// - `start: rtfm::Instant`. The time at which this handler started executing. **NOTE**: only -/// present if the `timer-queue` feature is enabled. -/// -/// - `resources: _`. Same meaning / function as [`init.resources`](#a-init). -/// -/// - `schedule: ::Schedule`. Same meaning / function as [`init.schedule`](#a-init). -/// -/// - `spawn: ::Spawn`. Same meaning / function as [`init.spawn`](#a-init). -/// -/// Other properties / constraints: -/// -/// - `exception` handlers can **not** be called from software. -/// -/// - The `static mut` variables declared at the beginning of this function will be transformed into -/// `&mut` references that are safe to access. For example, `static mut FOO: u32 = 0` will -/// become `FOO: &mut u32`. -/// -/// ## d. `#[interrupt]` -/// -/// This attribute indicates that the function is to be used as an *interrupt handler*, a type of -/// hardware task. The signature of `interrupt` handlers must be `[unsafe] fn()`. -/// -/// The name of the function must match one of the device specific interrupts. See your device crate -/// documentation (`Interrupt` enum) for more details. -/// -/// The `interrupt` attribute accepts the following optional arguments. -/// -/// - `priority = (..)`. Same meaning / function as [`#[exception].priority`](#b-exception). -/// -/// - `resources = (..)`. Same meaning / function as [`#[init].resources`](#a-init). -/// -/// - `schedule = (..)`. Same meaning / function as [`#[init].schedule`](#a-init). -/// -/// - `spawn = (..)`. Same meaning / function as [`#[init].spawn`](#a-init). -/// -/// The `app` attribute will injected a *context* into this function that comprises the following -/// variables: -/// -/// - `start: rtfm::Instant`. Same meaning / function as [`exception.start`](#b-exception). -/// -/// - `resources: _`. Same meaning / function as [`init.resources`](#a-init). -/// -/// - `schedule: ::Schedule`. Same meaning / function as [`init.schedule`](#a-init). -/// -/// - `spawn: ::Spawn`. Same meaning / function as [`init.spawn`](#a-init). -/// -/// Other properties / constraints: -/// -/// - `interrupt` handlers can **not** be called from software, but they can be [`pend`]-ed by the -/// software from any context. -/// -/// [`pend`]: ../rtfm/fn.pend.html -/// -/// - The `static mut` variables declared at the beginning of this function will be transformed into -/// `&mut` references that are safe to access. For example, `static mut FOO: u32 = 0` will -/// become `FOO: &mut u32`. -/// -/// ## e. `#[task]` -/// -/// This attribute indicates that the function is to be used as a *software task*. The signature of -/// software `task`s must be `[unsafe] fn()`. -/// -/// The `task` attribute accepts the following optional arguments. -/// -/// - `capacity = `. The maximum number of instances of this task that can be queued onto -/// the task scheduler for execution. The value must be in the range `1..=255`. If the `capacity` -/// argument is omitted then the capacity will be inferred. -/// -/// - `priority = `. Same meaning / function as [`#[exception].priority`](#b-exception). -/// -/// - `resources = (..)`. Same meaning / function as [`#[init].resources`](#a-init). -/// -/// - `schedule = (..)`. Same meaning / function as [`#[init].schedule`](#a-init). -/// -/// - `spawn = (..)`. Same meaning / function as [`#[init].spawn`](#a-init). -/// -/// The `app` attribute will injected a *context* into this function that comprises the following -/// variables: -/// -/// - `scheduled: rtfm::Instant`. The time at which this task was scheduled to run. **NOTE**: Only -/// present if `timer-queue` is enabled. -/// -/// - `resources: _`. Same meaning / function as [`init.resources`](#a-init). -/// -/// - `schedule: ::Schedule`. Same meaning / function as [`init.schedule`](#a-init). -/// -/// - `spawn: ::Spawn`. Same meaning / function as [`init.spawn`](#a-init). -/// -/// Other properties / constraints: -/// -/// - Software `task`s can **not** be called from software, but they can be `spawn`-ed and -/// `schedule`-d by the software from any context. -/// -/// - The `static mut` variables declared at the beginning of this function will be transformed into -/// `&mut` references that are safe to access. For example, `static mut FOO: u32 = 0` will -/// become `FOO: &mut u32`. -/// -/// # 3. `extern` block -/// -/// This `extern` block contains a list of interrupts which are *not* used by the application as -/// hardware tasks. These interrupts will be used to dispatch software tasks. Each interrupt will be -/// used to dispatch *multiple* software tasks *at the same priority level*. -/// -/// This `extern` block must only contain functions with signature `fn ()`. The names of these -/// functions must match the names of the target device interrupts. -/// -/// Importantly, attributes can be applied to the functions inside this block. These attributes will -/// be forwarded to the interrupt handlers generated by the `app` attribute. #[proc_macro_attribute] pub fn app(args: TokenStream, input: TokenStream) -> TokenStream { - // Parse - let args = parse_macro_input!(args as syntax::AppArgs); - let input = parse_macro_input!(input as syntax::Input); - - let app = match syntax::App::parse(input.items, args) { + let (app, analysis) = match rtfm_syntax::parse( + args, + input, + Settings { + parse_cores: cfg!(feature = "heterogeneous"), + parse_exception: true, + parse_extern_interrupt: true, + parse_interrupt: true, + parse_schedule: true, + optimize_priorities: true, + ..Settings::default() + }, + ) { Err(e) => return e.to_compile_error().into(), - Ok(app) => app, + Ok(x) => x, }; - // Check the specification - if let Err(e) = check::app(&app) { - return e.to_compile_error().into(); - } + let extra = match check::app(&app, &analysis) { + Err(e) => return e.to_compile_error().into(), + Ok(x) => x, + }; - // Ceiling analysis - let analysis = analyze::app(&app); + let analysis = analyze::app(analysis, &app); - // Code generation - let ts = codegen::app(&input.ident, &app, &analysis); + let ts = codegen::app(&app, &analysis, &extra); // Try to write the expanded code to disk if Path::new("target").exists() { diff --git a/macros/src/syntax.rs b/macros/src/syntax.rs deleted file mode 100644 index c6814d5..0000000 --- a/macros/src/syntax.rs +++ /dev/null @@ -1,1382 +0,0 @@ -use std::{ - collections::{BTreeMap, BTreeSet}, - iter, u8, -}; - -use proc_macro2::Span; -use syn::{ - braced, bracketed, parenthesized, - parse::{self, Parse, ParseStream}, - punctuated::Punctuated, - spanned::Spanned, - token::Brace, - ArgCaptured, AttrStyle, Attribute, Expr, FnArg, ForeignItem, Ident, IntSuffix, Item, ItemFn, - ItemForeignMod, ItemStatic, LitInt, Pat, Path, PathArguments, ReturnType, Stmt, Token, Type, - TypeTuple, Visibility, -}; - -pub struct AppArgs { - pub device: Path, -} - -impl Parse for AppArgs { - fn parse(input: ParseStream<'_>) -> parse::Result { - let mut device = None; - loop { - if input.is_empty() { - break; - } - - // #ident = .. - let ident: Ident = input.parse()?; - let _eq_token: Token![=] = input.parse()?; - - let ident_s = ident.to_string(); - match &*ident_s { - "device" => { - if device.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - device = Some(input.parse()?); - } - _ => { - return Err(parse::Error::new( - ident.span(), - "expected `device`; other keys are not accepted", - )); - } - } - - if input.is_empty() { - break; - } - - // , - let _: Token![,] = input.parse()?; - } - - Ok(AppArgs { - device: device.ok_or(parse::Error::new( - Span::call_site(), - "`device` argument is required", - ))?, - }) - } -} - -pub struct Input { - _const_token: Token![const], - pub ident: Ident, - _colon_token: Token![:], - _ty: TypeTuple, - _eq_token: Token![=], - _brace_token: Brace, - pub items: Vec, - _semi_token: Token![;], -} - -impl Parse for Input { - fn parse(input: ParseStream<'_>) -> parse::Result { - fn parse_items(input: ParseStream<'_>) -> parse::Result> { - let mut items = vec![]; - - while !input.is_empty() { - items.push(input.parse()?); - } - - Ok(items) - } - - let content; - Ok(Input { - _const_token: input.parse()?, - ident: input.parse()?, - _colon_token: input.parse()?, - _ty: input.parse()?, - _eq_token: input.parse()?, - _brace_token: braced!(content in input), - items: content.call(parse_items)?, - _semi_token: input.parse()?, - }) - } -} - -pub struct App { - pub args: AppArgs, - pub idle: Option, - pub init: Init, - pub exceptions: Exceptions, - pub interrupts: Interrupts, - pub resources: Resources, - pub tasks: Tasks, - pub free_interrupts: FreeInterrupts, -} - -impl App { - pub fn parse(items: Vec, args: AppArgs) -> parse::Result { - let mut idle = None; - let mut init = None; - let mut exceptions = BTreeMap::new(); - let mut interrupts = BTreeMap::new(); - let mut resources = BTreeMap::new(); - let mut tasks = BTreeMap::new(); - let mut free_interrupts = None; - - for item in items { - match item { - Item::Fn(mut item) => { - if let Some(pos) = item.attrs.iter().position(|attr| eq(attr, "idle")) { - if idle.is_some() { - return Err(parse::Error::new( - item.span(), - "`#[idle]` function must appear at most once", - )); - } - - let args = syn::parse2(item.attrs.swap_remove(pos).tts)?; - - idle = Some(Idle::check(args, item)?); - } else if let Some(pos) = item.attrs.iter().position(|attr| eq(attr, "init")) { - if init.is_some() { - return Err(parse::Error::new( - item.span(), - "`#[init]` function must appear exactly once", - )); - } - - let args = syn::parse2(item.attrs.swap_remove(pos).tts)?; - - init = Some(Init::check(args, item)?); - } else if let Some(pos) = - item.attrs.iter().position(|attr| eq(attr, "exception")) - { - if exceptions.contains_key(&item.ident) - || interrupts.contains_key(&item.ident) - || tasks.contains_key(&item.ident) - { - return Err(parse::Error::new( - item.ident.span(), - "this task is defined multiple times", - )); - } - - let args = syn::parse2(item.attrs.swap_remove(pos).tts)?; - - exceptions.insert(item.ident.clone(), Exception::check(args, item)?); - } else if let Some(pos) = - item.attrs.iter().position(|attr| eq(attr, "interrupt")) - { - if exceptions.contains_key(&item.ident) - || interrupts.contains_key(&item.ident) - || tasks.contains_key(&item.ident) - { - return Err(parse::Error::new( - item.ident.span(), - "this task is defined multiple times", - )); - } - - let args = syn::parse2(item.attrs.swap_remove(pos).tts)?; - - interrupts.insert(item.ident.clone(), Interrupt::check(args, item)?); - } else if let Some(pos) = item.attrs.iter().position(|attr| eq(attr, "task")) { - if exceptions.contains_key(&item.ident) - || interrupts.contains_key(&item.ident) - || tasks.contains_key(&item.ident) - { - return Err(parse::Error::new( - item.ident.span(), - "this task is defined multiple times", - )); - } - - let args = syn::parse2(item.attrs.swap_remove(pos).tts)?; - - tasks.insert(item.ident.clone(), Task::check(args, item)?); - } else { - return Err(parse::Error::new( - item.span(), - "this item must live outside the `#[app]` module", - )); - } - } - Item::Static(item) => { - if resources.contains_key(&item.ident) { - return Err(parse::Error::new( - item.ident.span(), - "this resource is listed twice", - )); - } - - resources.insert(item.ident.clone(), Resource::check(item)?); - } - Item::ForeignMod(item) => { - if free_interrupts.is_some() { - return Err(parse::Error::new( - item.abi.extern_token.span(), - "`extern` block can only appear at most once", - )); - } - - free_interrupts = Some(FreeInterrupt::parse(item)?); - } - _ => { - return Err(parse::Error::new( - item.span(), - "this item must live outside the `#[app]` module", - )); - } - } - } - - Ok(App { - args, - idle, - init: init.ok_or_else(|| { - parse::Error::new(Span::call_site(), "`#[init]` function is missing") - })?, - exceptions, - interrupts, - resources, - tasks, - free_interrupts: free_interrupts.unwrap_or_else(|| FreeInterrupts::new()), - }) - } - - /// Returns an iterator over all resource accesses. - /// - /// Each resource access include the priority it's accessed at (`u8`) and the name of the - /// resource (`Ident`). A resource may appear more than once in this iterator - pub fn resource_accesses(&self) -> impl Iterator { - self.idle - .as_ref() - .map(|idle| -> Box> { - Box::new(idle.args.resources.iter().map(|res| (0, res))) - }) - .unwrap_or_else(|| Box::new(iter::empty())) - .chain(self.exceptions.values().flat_map(|e| { - e.args - .resources - .iter() - .map(move |res| (e.args.priority, res)) - })) - .chain(self.interrupts.values().flat_map(|i| { - i.args - .resources - .iter() - .map(move |res| (i.args.priority, res)) - })) - .chain(self.tasks.values().flat_map(|t| { - t.args - .resources - .iter() - .map(move |res| (t.args.priority, res)) - })) - } - - /// Returns an iterator over all `spawn` calls - /// - /// Each spawn call includes the priority of the task from which it's issued and the name of the - /// task that's spawned. A task may appear more that once in this iterator. - /// - /// A priority of `None` means that this being called from `init` - pub fn spawn_calls(&self) -> impl Iterator, &Ident)> { - self.init - .args - .spawn - .iter() - .map(|s| (None, s)) - .chain( - self.idle - .as_ref() - .map(|idle| -> Box> { - Box::new(idle.args.spawn.iter().map(|s| (Some(0), s))) - }) - .unwrap_or_else(|| Box::new(iter::empty())), - ) - .chain( - self.exceptions - .values() - .flat_map(|e| e.args.spawn.iter().map(move |s| (Some(e.args.priority), s))), - ) - .chain( - self.interrupts - .values() - .flat_map(|i| i.args.spawn.iter().map(move |s| (Some(i.args.priority), s))), - ) - .chain( - self.tasks - .values() - .flat_map(|t| t.args.spawn.iter().map(move |s| (Some(t.args.priority), s))), - ) - } - - /// Returns an iterator over all `schedule` calls - /// - /// Each spawn call includes the priority of the task from which it's issued and the name of the - /// task that's spawned. A task may appear more that once in this iterator. - #[allow(dead_code)] - pub fn schedule_calls(&self) -> impl Iterator, &Ident)> { - self.init - .args - .schedule - .iter() - .map(|s| (None, s)) - .chain( - self.idle - .as_ref() - .map(|idle| -> Box> { - Box::new(idle.args.schedule.iter().map(|s| (Some(0), s))) - }) - .unwrap_or_else(|| Box::new(iter::empty())), - ) - .chain(self.exceptions.values().flat_map(|e| { - e.args - .schedule - .iter() - .map(move |s| (Some(e.args.priority), s)) - })) - .chain(self.interrupts.values().flat_map(|i| { - i.args - .schedule - .iter() - .map(move |s| (Some(i.args.priority), s)) - })) - .chain(self.tasks.values().flat_map(|t| { - t.args - .schedule - .iter() - .map(move |s| (Some(t.args.priority), s)) - })) - } - - #[allow(dead_code)] - pub fn schedule_callers(&self) -> impl Iterator { - self.idle - .as_ref() - .map(|idle| -> Box> { - Box::new(iter::once(( - Ident::new("idle", Span::call_site()), - &idle.args.schedule, - ))) - }) - .unwrap_or_else(|| Box::new(iter::empty())) - .chain(iter::once(( - Ident::new("init", Span::call_site()), - &self.init.args.schedule, - ))) - .chain( - self.exceptions - .iter() - .map(|(name, exception)| (name.clone(), &exception.args.schedule)), - ) - .chain( - self.interrupts - .iter() - .map(|(name, interrupt)| (name.clone(), &interrupt.args.schedule)), - ) - .chain( - self.tasks - .iter() - .map(|(name, task)| (name.clone(), &task.args.schedule)), - ) - } - - pub fn spawn_callers(&self) -> impl Iterator { - self.idle - .as_ref() - .map(|idle| -> Box> { - Box::new(iter::once(( - Ident::new("idle", Span::call_site()), - &idle.args.spawn, - ))) - }) - .unwrap_or_else(|| Box::new(iter::empty())) - .chain(iter::once(( - Ident::new("init", Span::call_site()), - &self.init.args.spawn, - ))) - .chain( - self.exceptions - .iter() - .map(|(name, exception)| (name.clone(), &exception.args.spawn)), - ) - .chain( - self.interrupts - .iter() - .map(|(name, interrupt)| (name.clone(), &interrupt.args.spawn)), - ) - .chain( - self.tasks - .iter() - .map(|(name, task)| (name.clone(), &task.args.spawn)), - ) - } -} - -pub type Idents = BTreeSet; - -pub type Exceptions = BTreeMap; - -pub type Interrupts = BTreeMap; - -pub type Resources = BTreeMap; - -pub type Statics = Vec; - -pub type Tasks = BTreeMap; - -pub type FreeInterrupts = BTreeMap; - -pub struct Idle { - pub args: IdleArgs, - pub attrs: Vec, - pub context: Pat, - pub statics: BTreeMap, - pub stmts: Vec, -} - -pub type IdleArgs = InitArgs; - -impl Idle { - fn check(args: IdleArgs, item: ItemFn) -> parse::Result { - let valid_signature = - check_signature(&item) && item.decl.inputs.len() == 1 && is_bottom(&item.decl.output); - - let span = item.span(); - - 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, - }); - } - } - - Err(parse::Error::new( - span, - "`idle` must have type signature `fn(idle::Context) -> !`", - )) - } -} - -pub struct InitArgs { - pub resources: Idents, - pub schedule: Idents, - pub spawn: Idents, -} - -impl Default for InitArgs { - fn default() -> Self { - InitArgs { - resources: Idents::new(), - schedule: Idents::new(), - spawn: Idents::new(), - } - } -} - -impl Parse for InitArgs { - fn parse(input: ParseStream<'_>) -> parse::Result { - if input.is_empty() { - return Ok(InitArgs::default()); - } - - let mut resources = None; - let mut schedule = None; - let mut spawn = None; - - let content; - parenthesized!(content in input); - loop { - if content.is_empty() { - break; - } - - // #ident = .. - let ident: Ident = content.parse()?; - let _: Token![=] = content.parse()?; - - let ident_s = ident.to_string(); - match &*ident_s { - "schedule" if cfg!(not(feature = "timer-queue")) => { - return Err(parse::Error::new( - ident.span(), - "The `schedule` API requires that the `timer-queue` feature is \ - enabled in the `cortex-m-rtfm` crate", - )); - } - "resources" | "schedule" | "spawn" => {} // OK - _ => { - return Err(parse::Error::new( - ident.span(), - "expected one of: resources, schedule or spawn", - )); - } - } - - // .. [#(#idents)*] - let inner; - bracketed!(inner in content); - let mut idents = Idents::new(); - for ident in inner.call(Punctuated::<_, Token![,]>::parse_terminated)? { - if idents.contains(&ident) { - return Err(parse::Error::new( - ident.span(), - "element appears more than once in list", - )); - } - - idents.insert(ident); - } - - let ident_s = ident.to_string(); - match &*ident_s { - "resources" => { - if resources.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - resources = Some(idents); - } - "schedule" => { - if schedule.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - schedule = Some(idents); - } - "spawn" => { - if spawn.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - spawn = Some(idents); - } - _ => unreachable!(), - } - - if content.is_empty() { - break; - } - - // , - let _: Token![,] = content.parse()?; - } - - Ok(InitArgs { - resources: resources.unwrap_or(Idents::new()), - schedule: schedule.unwrap_or(Idents::new()), - spawn: spawn.unwrap_or(Idents::new()), - }) - } -} - -pub struct Init { - pub args: InitArgs, - pub attrs: Vec, - pub statics: BTreeMap, - pub context: Pat, - pub stmts: Vec, - pub returns_late_resources: bool, - pub span: Span, -} - -impl Init { - fn check(args: InitArgs, item: ItemFn) -> parse::Result { - 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, - ReturnType::Type(_, ty) => { - match &**ty { - Type::Tuple(t) => { - if t.elems.is_empty() { - // -> () - true - } else { - valid_signature = false; - - DONT_CARE - } - } - - Type::Path(_) => { - if is_path(ty, &["init", "LateResources"]) { - // -> init::LateResources - true - } else { - valid_signature = false; - - DONT_CARE - } - } - - _ => { - valid_signature = false; - - DONT_CARE - } - } - } - }; - - let span = item.span(); - - 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, - }); - } - } - - Err(parse::Error::new( - span, - "`init` must have type signature `fn(init::Context) [-> init::LateResources]`", - )) - } -} - -/// Union of `TaskArgs`, `ExceptionArgs` and `InterruptArgs` -pub struct Args { - pub binds: Option, - pub capacity: Option, - pub priority: u8, - pub resources: Idents, - pub schedule: Idents, - pub spawn: Idents, -} - -impl Default for Args { - fn default() -> Self { - Args { - binds: None, - capacity: None, - priority: 1, - resources: Idents::new(), - schedule: Idents::new(), - spawn: Idents::new(), - } - } -} - -pub struct Exception { - pub args: ExceptionArgs, - pub attrs: Vec, - pub statics: BTreeMap, - pub context: Pat, - pub stmts: Vec, -} - -pub struct ExceptionArgs { - binds: Option, - pub priority: u8, - pub resources: Idents, - pub schedule: Idents, - pub spawn: Idents, -} - -impl ExceptionArgs { - /// Returns the name of the exception / interrupt this handler binds to - pub fn binds<'a>(&'a self, handler: &'a Ident) -> &'a Ident { - self.binds.as_ref().unwrap_or(handler) - } -} - -impl Parse for ExceptionArgs { - fn parse(input: ParseStream<'_>) -> parse::Result { - parse_args(input, /* binds */ true, /* capacity */ false).map( - |Args { - binds, - priority, - resources, - schedule, - spawn, - .. - }| { - ExceptionArgs { - binds, - priority, - resources, - schedule, - spawn, - } - }, - ) - } -} - -impl Exception { - fn check(args: ExceptionArgs, item: ItemFn) -> parse::Result { - let valid_signature = - check_signature(&item) && item.decl.inputs.len() == 1 && is_unit(&item.decl.output); - - 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, - "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, - }); - } - } - - Err(parse::Error::new( - span, - &format!( - "this `exception` handler must have type signature `fn({}::Context)`", - name - ), - )) - } -} - -pub struct Interrupt { - pub args: InterruptArgs, - pub attrs: Vec, - pub statics: BTreeMap, - pub context: Pat, - pub stmts: Vec, -} - -pub type InterruptArgs = ExceptionArgs; - -impl Interrupt { - fn check(args: InterruptArgs, item: ItemFn) -> parse::Result { - let valid_signature = - check_signature(&item) && item.decl.inputs.len() == 1 && is_unit(&item.decl.output); - - let span = item.span(); - - 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`", - )); - } - _ => {} - } - - let (statics, stmts) = extract_statics(item.block.stmts); - - return Ok(Interrupt { - args, - attrs: item.attrs, - statics: Static::parse(statics)?, - context, - stmts, - }); - } - } - - Err(parse::Error::new( - span, - format!( - "this `interrupt` handler must have type signature `fn({}::Context)`", - name - ), - )) - } -} - -pub struct Resource { - pub cfgs: Vec, - pub attrs: Vec, - pub mutability: Option, - pub ty: Box, - pub expr: Option>, -} - -impl Resource { - fn check(item: ItemStatic) -> parse::Result { - if item.vis != Visibility::Inherited { - return Err(parse::Error::new( - item.span(), - "resources must have inherited / private visibility", - )); - } - - let uninitialized = match *item.expr { - Expr::Tuple(ref tuple) => tuple.elems.is_empty(), - _ => false, - }; - - let (cfgs, attrs) = extract_cfgs(item.attrs); - - Ok(Resource { - cfgs, - attrs, - mutability: item.mutability, - ty: item.ty, - expr: if uninitialized { None } else { Some(item.expr) }, - }) - } -} - -pub struct TaskArgs { - pub capacity: Option, - pub priority: u8, - pub resources: Idents, - pub spawn: Idents, - pub schedule: Idents, -} - -impl Parse for TaskArgs { - fn parse(input: ParseStream<'_>) -> parse::Result { - parse_args(input, /* binds */ false, /* capacity */ true).map( - |Args { - capacity, - priority, - resources, - schedule, - spawn, - .. - }| { - TaskArgs { - capacity, - priority, - resources, - schedule, - spawn, - } - }, - ) - } -} - -// Parser shared by ExceptionArgs, InterruptArgs and TaskArgs -fn parse_args( - input: ParseStream<'_>, - accepts_binds: bool, - accepts_capacity: bool, -) -> parse::Result { - if input.is_empty() { - return Ok(Args::default()); - } - - let mut binds = None; - let mut capacity = None; - let mut priority = None; - let mut resources = None; - let mut schedule = None; - let mut spawn = None; - - let content; - parenthesized!(content in input); - loop { - if content.is_empty() { - break; - } - - // #ident = .. - let ident: Ident = content.parse()?; - let _: Token![=] = content.parse()?; - - let ident_s = ident.to_string(); - match &*ident_s { - "binds" if accepts_binds => { - if binds.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - // #ident - let ident = content.parse()?; - - binds = Some(ident); - } - "capacity" if accepts_capacity => { - if capacity.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - // #lit - let lit: LitInt = content.parse()?; - - if lit.suffix() != IntSuffix::None { - return Err(parse::Error::new( - lit.span(), - "this literal must be unsuffixed", - )); - } - - let value = lit.value(); - if value > u64::from(u8::MAX) || value == 0 { - return Err(parse::Error::new( - lit.span(), - "this literal must be in the range 1...255", - )); - } - - capacity = Some(value as u8); - } - "priority" => { - if priority.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - // #lit - let lit: LitInt = content.parse()?; - - if lit.suffix() != IntSuffix::None { - return Err(parse::Error::new( - lit.span(), - "this literal must be unsuffixed", - )); - } - - let value = lit.value(); - if value > u64::from(u8::MAX) || value == 0 { - return Err(parse::Error::new( - lit.span(), - "this literal must be in the range 1...255", - )); - } - - priority = Some(value as u8); - } - "schedule" if cfg!(not(feature = "timer-queue")) => { - return Err(parse::Error::new( - ident.span(), - "The `schedule` API requires that the `timer-queue` feature is \ - enabled in the `cortex-m-rtfm` crate", - )); - } - "resources" | "schedule" | "spawn" => { - // .. [#(#idents)*] - let inner; - bracketed!(inner in content); - let mut idents = Idents::new(); - for ident in inner.call(Punctuated::<_, Token![,]>::parse_terminated)? { - if idents.contains(&ident) { - return Err(parse::Error::new( - ident.span(), - "element appears more than once in list", - )); - } - - idents.insert(ident); - } - - match &*ident_s { - "resources" => { - if resources.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - resources = Some(idents); - } - "schedule" => { - if schedule.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - schedule = Some(idents); - } - "spawn" => { - if spawn.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - spawn = Some(idents); - } - _ => unreachable!(), - } - } - _ => { - return Err(parse::Error::new( - ident.span(), - format!( - "expected one of: {}{}priority, resources, schedule or spawn", - if accepts_binds { "binds, " } else { "" }, - if accepts_capacity { "capacity, " } else { "" }, - ), - )); - } - } - - if content.is_empty() { - break; - } - - // , - let _: Token![,] = content.parse()?; - } - - Ok(Args { - binds, - capacity, - priority: priority.unwrap_or(1), - resources: resources.unwrap_or(Idents::new()), - schedule: schedule.unwrap_or(Idents::new()), - spawn: spawn.unwrap_or(Idents::new()), - }) -} - -pub struct Static { - /// `#[cfg]` attributes - pub cfgs: Vec, - /// Attributes that are not `#[cfg]` - pub attrs: Vec, - pub ty: Box, - pub expr: Box, -} - -impl Static { - fn parse(items: Vec) -> parse::Result> { - let mut statics = BTreeMap::new(); - - for item in items { - if statics.contains_key(&item.ident) { - return Err(parse::Error::new( - item.ident.span(), - "this `static` is listed twice", - )); - } - - let (cfgs, attrs) = extract_cfgs(item.attrs); - - statics.insert( - item.ident, - Static { - cfgs, - attrs, - ty: item.ty, - expr: item.expr, - }, - ); - } - - Ok(statics) - } -} - -pub struct Task { - pub args: TaskArgs, - pub cfgs: Vec, - pub attrs: Vec, - pub inputs: Vec, - pub context: Pat, - pub statics: BTreeMap, - pub stmts: Vec, -} - -impl Task { - fn check(args: TaskArgs, item: ItemFn) -> parse::Result { - let valid_signature = - check_signature(&item) && !item.decl.inputs.is_empty() && is_unit(&item.decl.output); - - let span = item.span(); - - 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 inputs = rest.map_err(|arg| { - parse::Error::new( - arg.span(), - "inputs must be named arguments (e.f. `foo: u32`) and not include `self`", - ) - })?; - - match &*name { - "init" | "idle" | "resources" => { - return Err(parse::Error::new( - span, - "`task` handlers can NOT be named `idle`, `init` or `resources`", - )); - } - _ => {} - } - - let (cfgs, attrs) = extract_cfgs(item.attrs); - return Ok(Task { - args, - cfgs, - attrs, - inputs, - context, - statics: Static::parse(statics)?, - stmts, - }); - } - } - - Err(parse::Error::new( - span, - &format!( - "this `task` handler must have type signature `fn({}::Context, ..)`", - name - ), - )) - } -} - -pub struct FreeInterrupt { - pub attrs: Vec, -} - -impl FreeInterrupt { - fn parse(mod_: ItemForeignMod) -> parse::Result { - let mut free_interrupts = FreeInterrupts::new(); - - for item in mod_.items { - if let ForeignItem::Fn(f) = item { - let valid_signature = f.vis == Visibility::Inherited - && f.decl.generics.params.is_empty() - && f.decl.generics.where_clause.is_none() - && f.decl.inputs.is_empty() - && f.decl.variadic.is_none() - && is_unit(&f.decl.output); - - if !valid_signature { - return Err(parse::Error::new( - f.span(), - "free interrupts must have type signature `fn()`", - )); - } - - if free_interrupts.contains_key(&f.ident) { - return Err(parse::Error::new( - f.ident.span(), - "this interrupt appears twice", - )); - } - - free_interrupts.insert(f.ident, FreeInterrupt { attrs: f.attrs }); - } else { - return Err(parse::Error::new( - mod_.abi.extern_token.span(), - "`extern` block should only contains functions", - )); - } - } - - Ok(free_interrupts) - } -} - -fn eq(attr: &Attribute, name: &str) -> bool { - attr.style == AttrStyle::Outer && attr.path.segments.len() == 1 && { - let pair = attr.path.segments.first().unwrap(); - let segment = pair.value(); - segment.arguments == PathArguments::None && segment.ident.to_string() == name - } -} - -fn extract_cfgs(attrs: Vec) -> (Vec, Vec) { - let mut cfgs = vec![]; - let mut not_cfgs = vec![]; - - for attr in attrs { - if eq(&attr, "cfg") { - cfgs.push(attr); - } else { - not_cfgs.push(attr); - } - } - - (cfgs, not_cfgs) -} - -/// Extracts `static mut` vars from the beginning of the given statements -fn extract_statics(stmts: Vec) -> (Statics, Vec) { - let mut istmts = stmts.into_iter(); - - let mut statics = Statics::new(); - let mut stmts = vec![]; - while let Some(stmt) = istmts.next() { - match stmt { - Stmt::Item(Item::Static(var)) => { - if var.mutability.is_some() { - statics.push(var); - } else { - stmts.push(Stmt::Item(Item::Static(var))); - break; - } - } - _ => { - stmts.push(stmt); - break; - } - } - } - - stmts.extend(istmts); - - (statics, stmts) -} - -// checks that the list of arguments has the form `#pat: #name::Context, (..)` -// -// if the check succeeds it returns `#pat` plus the remaining arguments -fn check_inputs( - inputs: Punctuated, - name: &str, -) -> Option<(Pat, Result, FnArg>)> { - let mut inputs = inputs.into_iter(); - - 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::, _>>(); - - Some((first.pat, rest)) - } else { - None - } - } - - _ => None, - } -} - -/// 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) - } - - _ => false, - } -} - -fn is_bottom(ty: &ReturnType) -> bool { - if let ReturnType::Type(_, ty) = ty { - if let Type::Never(_) = **ty { - true - } else { - false - } - } else { - false - } -} - -fn is_unit(ty: &ReturnType) -> bool { - if let ReturnType::Type(_, ty) = ty { - if let Type::Tuple(ref tuple) = **ty { - tuple.elems.is_empty() - } else { - false - } - } else { - true - } -} diff --git a/macros/src/tests.rs b/macros/src/tests.rs new file mode 100644 index 0000000..470c905 --- /dev/null +++ b/macros/src/tests.rs @@ -0,0 +1,5 @@ +// NOTE these tests are specific to the Cortex-M port; `rtfm-syntax` has a more extensive test suite +// that tests functionality common to all the RTFM ports + +mod multi; +mod single; diff --git a/macros/src/tests/multi.rs b/macros/src/tests/multi.rs new file mode 100644 index 0000000..37fef53 --- /dev/null +++ b/macros/src/tests/multi.rs @@ -0,0 +1,59 @@ +use quote::quote; +use rtfm_syntax::Settings; + +#[test] +fn analyze() { + let (app, analysis) = rtfm_syntax::parse2( + quote!(device = pac, cores = 2), + quote!( + const APP: () = { + #[task(core = 0, priority = 1)] + fn a(_: a::Context) {} + + #[task(core = 0, priority = 2)] + fn b(_: b::Context) {} + + #[task(core = 1, priority = 1)] + fn c(_: c::Context) {} + + #[task(core = 1, priority = 2)] + fn d(_: d::Context) {} + + // first interrupt is assigned to the highest priority dispatcher + extern "C" { + #[core = 0] + fn B(); + + #[core = 0] + fn A(); + + #[core = 1] + fn A(); + + #[core = 1] + fn C(); + } + }; + ), + Settings { + parse_cores: true, + parse_extern_interrupt: true, + ..Settings::default() + }, + ) + .unwrap(); + + let analysis = crate::analyze::app(analysis, &app); + + // first core + let interrupts0 = &analysis.interrupts[&0]; + assert_eq!(interrupts0.len(), 2); + assert_eq!(interrupts0[&2].to_string(), "B"); + assert_eq!(interrupts0[&1].to_string(), "A"); + + // second core + let interrupts1 = &analysis.interrupts[&1]; + assert_eq!(interrupts1.len(), 2); + assert_eq!(interrupts1[&2].to_string(), "A"); + assert_eq!(interrupts1[&1].to_string(), "C"); +} diff --git a/macros/src/tests/single.rs b/macros/src/tests/single.rs new file mode 100644 index 0000000..fb2d430 --- /dev/null +++ b/macros/src/tests/single.rs @@ -0,0 +1,35 @@ +use quote::quote; +use rtfm_syntax::Settings; + +#[test] +fn analyze() { + let (app, analysis) = rtfm_syntax::parse2( + quote!(device = pac), + quote!( + const APP: () = { + #[task(priority = 1)] + fn a(_: a::Context) {} + + #[task(priority = 2)] + fn b(_: b::Context) {} + + // first interrupt is assigned to the highest priority dispatcher + extern "C" { + fn B(); + fn A(); + } + }; + ), + Settings { + parse_extern_interrupt: true, + ..Settings::default() + }, + ) + .unwrap(); + + let analysis = crate::analyze::app(analysis, &app); + let interrupts = &analysis.interrupts[&0]; + assert_eq!(interrupts.len(), 2); + assert_eq!(interrupts[&2].to_string(), "B"); + assert_eq!(interrupts[&1].to_string(), "A"); +} -- cgit v1.2.3 From 9897728709528a02545523bea72576abce89dc4c Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Tue, 18 Jun 2019 10:31:31 +0200 Subject: add homogeneous multi-core support --- Cargo.toml | 4 +- ci/script.sh | 4 +- heterogeneous/Cargo.toml | 18 +++++++ heterogeneous/README.md | 1 + heterogeneous/examples/smallest.rs | 7 +++ heterogeneous/examples/x-init-2.rs | 39 ++++++++++++++ heterogeneous/examples/x-init.rs | 26 ++++++++++ heterogeneous/examples/x-schedule.rs | 36 +++++++++++++ heterogeneous/examples/x-spawn.rs | 20 ++++++++ heterogeneous/src/lib.rs | 94 ++++++++++++++++++++++++++++++++++ homogeneous/Cargo.toml | 17 +++++++ homogeneous/README.md | 1 + homogeneous/examples/smallest.rs | 7 +++ homogeneous/examples/x-init-2.rs | 39 ++++++++++++++ homogeneous/examples/x-init.rs | 26 ++++++++++ homogeneous/examples/x-schedule.rs | 36 +++++++++++++ homogeneous/examples/x-spawn.rs | 20 ++++++++ homogeneous/src/lib.rs | 94 ++++++++++++++++++++++++++++++++++ macros/Cargo.toml | 1 + macros/src/check.rs | 22 ++++++++ macros/src/codegen.rs | 3 +- macros/src/codegen/dispatchers.rs | 10 +++- macros/src/codegen/hardware_tasks.rs | 6 ++- macros/src/codegen/post_init.rs | 18 ++++++- macros/src/codegen/pre_init.rs | 17 +++++-- macros/src/codegen/resources.rs | 8 ++- macros/src/codegen/software_tasks.rs | 8 ++- macros/src/codegen/spawn_body.rs | 5 +- macros/src/codegen/timer_queue.rs | 8 +-- macros/src/codegen/util.rs | 23 ++++++++- macros/src/lib.rs | 2 +- mc/Cargo.toml | 18 ------- mc/README.md | 1 - mc/examples/smallest.rs | 7 --- mc/examples/x-init-2.rs | 39 -------------- mc/examples/x-init.rs | 26 ---------- mc/examples/x-schedule.rs | 36 ------------- mc/examples/x-spawn.rs | 20 -------- mc/src/lib.rs | 99 ------------------------------------ src/lib.rs | 2 +- 40 files changed, 600 insertions(+), 268 deletions(-) create mode 100644 heterogeneous/Cargo.toml create mode 100644 heterogeneous/README.md create mode 100644 heterogeneous/examples/smallest.rs create mode 100644 heterogeneous/examples/x-init-2.rs create mode 100644 heterogeneous/examples/x-init.rs create mode 100644 heterogeneous/examples/x-schedule.rs create mode 100644 heterogeneous/examples/x-spawn.rs create mode 100644 heterogeneous/src/lib.rs create mode 100644 homogeneous/Cargo.toml create mode 100644 homogeneous/README.md create mode 100644 homogeneous/examples/smallest.rs create mode 100644 homogeneous/examples/x-init-2.rs create mode 100644 homogeneous/examples/x-init.rs create mode 100644 homogeneous/examples/x-schedule.rs create mode 100644 homogeneous/examples/x-spawn.rs create mode 100644 homogeneous/src/lib.rs delete mode 100644 mc/Cargo.toml delete mode 100644 mc/README.md delete mode 100644 mc/examples/smallest.rs delete mode 100644 mc/examples/x-init-2.rs delete mode 100644 mc/examples/x-init.rs delete mode 100644 mc/examples/x-schedule.rs delete mode 100644 mc/examples/x-spawn.rs delete mode 100644 mc/src/lib.rs (limited to 'macros') diff --git a/Cargo.toml b/Cargo.toml index 81ca256..ef45be8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,6 +74,7 @@ compiletest_rs = "0.3.22" [features] heterogeneous = ["cortex-m-rtfm-macros/heterogeneous", "microamp"] +homogeneous = ["cortex-m-rtfm-macros/homogeneous", "microamp"] # used for testing this crate; do not use in applications __v7 =[] @@ -83,6 +84,7 @@ lto = true [workspace] members = [ + "heterogeneous", + "homogeneous", "macros", - "mc", ] diff --git a/ci/script.sh b/ci/script.sh index a6485cf..1b3d561 100644 --- a/ci/script.sh +++ b/ci/script.sh @@ -43,7 +43,7 @@ main() { cargo test --test multi --features heterogeneous --target $T # multi-core compile-pass tests - pushd mc + pushd heterogeneous local exs=( smallest x-init-2 @@ -91,6 +91,8 @@ main() { cargo check --target $T --examples --features __v7 fi + cargo check -p homogeneous --target $T --examples + # run-pass tests case $T in thumbv6m-none-eabi | thumbv7m-none-eabi) diff --git a/heterogeneous/Cargo.toml b/heterogeneous/Cargo.toml new file mode 100644 index 0000000..fd05d07 --- /dev/null +++ b/heterogeneous/Cargo.toml @@ -0,0 +1,18 @@ +[package] +authors = ["Jorge Aparicio "] +edition = "2018" +name = "heterogeneous" +# this crate is only used for testing +publish = false +version = "0.0.0-alpha.0" + +[dependencies] +bare-metal = "0.2.4" + +[dependencies.cortex-m-rtfm] +path = ".." +features = ["heterogeneous"] + +[dev-dependencies] +panic-halt = "0.2.0" +microamp = "0.1.0-alpha.1" diff --git a/heterogeneous/README.md b/heterogeneous/README.md new file mode 100644 index 0000000..8e49ff8 --- /dev/null +++ b/heterogeneous/README.md @@ -0,0 +1 @@ +This directory contains *heterogeneous* multi-core compile pass tests. diff --git a/heterogeneous/examples/smallest.rs b/heterogeneous/examples/smallest.rs new file mode 100644 index 0000000..9b6bb82 --- /dev/null +++ b/heterogeneous/examples/smallest.rs @@ -0,0 +1,7 @@ +#![no_main] +#![no_std] + +use panic_halt as _; + +#[rtfm::app(cores = 2, device = heterogeneous)] +const APP: () = {}; diff --git a/heterogeneous/examples/x-init-2.rs b/heterogeneous/examples/x-init-2.rs new file mode 100644 index 0000000..b9c3919 --- /dev/null +++ b/heterogeneous/examples/x-init-2.rs @@ -0,0 +1,39 @@ +//! [compile-pass] Cross initialization of late resources + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_halt as _; + +#[rtfm::app(cores = 2, device = heterogeneous)] +const APP: () = { + extern "C" { + // owned by core #1 but initialized by core #0 + static mut X: u32; + + // owned by core #0 but initialized by core #1 + static mut Y: u32; + } + + #[init(core = 0, late = [X])] + fn a(_: a::Context) -> a::LateResources { + a::LateResources { X: 0 } + } + + #[idle(core = 0, resources = [Y])] + fn b(_: b::Context) -> ! { + loop {} + } + + #[init(core = 1)] + fn c(_: c::Context) -> c::LateResources { + c::LateResources { Y: 0 } + } + + #[idle(core = 1, resources = [X])] + fn d(_: d::Context) -> ! { + loop {} + } +}; diff --git a/heterogeneous/examples/x-init.rs b/heterogeneous/examples/x-init.rs new file mode 100644 index 0000000..53e7380 --- /dev/null +++ b/heterogeneous/examples/x-init.rs @@ -0,0 +1,26 @@ +//! [compile-pass] Split initialization of late resources + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_halt as _; + +#[rtfm::app(cores = 2, device = heterogeneous)] +const APP: () = { + extern "C" { + static mut X: u32; + static mut Y: u32; + } + + #[init(core = 0, late = [X])] + fn a(_: a::Context) -> a::LateResources { + a::LateResources { X: 0 } + } + + #[init(core = 1)] + fn b(_: b::Context) -> b::LateResources { + b::LateResources { Y: 0 } + } +}; diff --git a/heterogeneous/examples/x-schedule.rs b/heterogeneous/examples/x-schedule.rs new file mode 100644 index 0000000..cbfc01f --- /dev/null +++ b/heterogeneous/examples/x-schedule.rs @@ -0,0 +1,36 @@ +#![no_main] +#![no_std] + +use panic_halt as _; + +#[rtfm::app(cores = 2, device = heterogeneous, monotonic = heterogeneous::MT)] +const APP: () = { + #[init(core = 0, spawn = [ping])] + fn init(c: init::Context) { + c.spawn.ping().ok(); + } + + #[task(core = 0, schedule = [ping])] + fn pong(c: pong::Context) { + c.schedule.ping(c.scheduled + 1_000_000).ok(); + } + + #[task(core = 1, schedule = [pong])] + fn ping(c: ping::Context) { + c.schedule.pong(c.scheduled + 1_000_000).ok(); + } + + extern "C" { + #[core = 0] + fn I0(); + + #[core = 0] + fn I1(); + + #[core = 1] + fn I0(); + + #[core = 1] + fn I1(); + } +}; diff --git a/heterogeneous/examples/x-spawn.rs b/heterogeneous/examples/x-spawn.rs new file mode 100644 index 0000000..3fc64f6 --- /dev/null +++ b/heterogeneous/examples/x-spawn.rs @@ -0,0 +1,20 @@ +#![no_main] +#![no_std] + +use panic_halt as _; + +#[rtfm::app(cores = 2, device = heterogeneous)] +const APP: () = { + #[init(core = 0, spawn = [foo])] + fn init(c: init::Context) { + c.spawn.foo().ok(); + } + + #[task(core = 1)] + fn foo(_: foo::Context) {} + + extern "C" { + #[core = 1] + fn I0(); + } +}; diff --git a/heterogeneous/src/lib.rs b/heterogeneous/src/lib.rs new file mode 100644 index 0000000..a4f0ec5 --- /dev/null +++ b/heterogeneous/src/lib.rs @@ -0,0 +1,94 @@ +//! Fake multi-core PAC + +#![no_std] + +use core::{ + cmp::Ordering, + ops::{Add, Sub}, +}; + +use bare_metal::Nr; +use rtfm::Monotonic; + +// both cores have the exact same interrupts +pub use Interrupt_0 as Interrupt_1; + +// Fake priority bits +pub const NVIC_PRIO_BITS: u8 = 3; + +pub fn xpend(_core: u8, _interrupt: impl Nr) {} + +/// Fake monotonic timer +pub struct MT; + +unsafe impl Monotonic for MT { + type Instant = Instant; + + fn ratio() -> u32 { + 1 + } + + unsafe fn reset() { + (0xE0001004 as *mut u32).write_volatile(0) + } + + fn now() -> Instant { + unsafe { Instant((0xE0001004 as *const u32).read_volatile() as i32) } + } + + fn zero() -> Instant { + Instant(0) + } +} + +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct Instant(i32); + +impl Add for Instant { + type Output = Instant; + + fn add(self, rhs: u32) -> Self { + Instant(self.0.wrapping_add(rhs as i32)) + } +} + +impl Sub for Instant { + type Output = u32; + + fn sub(self, rhs: Self) -> u32 { + self.0.checked_sub(rhs.0).unwrap() as u32 + } +} + +impl Ord for Instant { + fn cmp(&self, rhs: &Self) -> Ordering { + self.0.wrapping_sub(rhs.0).cmp(&0) + } +} + +impl PartialOrd for Instant { + fn partial_cmp(&self, rhs: &Self) -> Option { + Some(self.cmp(rhs)) + } +} + +// Fake interrupts +#[allow(non_camel_case_types)] +#[derive(Clone, Copy)] +#[repr(u8)] +pub enum Interrupt_0 { + I0 = 0, + I1 = 1, + I2 = 2, + I3 = 3, + I4 = 4, + I5 = 5, + I6 = 6, + I7 = 7, +} + +unsafe impl Nr for Interrupt_0 { + fn nr(&self) -> u8 { + *self as u8 + } +} diff --git a/homogeneous/Cargo.toml b/homogeneous/Cargo.toml new file mode 100644 index 0000000..210ee2e --- /dev/null +++ b/homogeneous/Cargo.toml @@ -0,0 +1,17 @@ +[package] +authors = ["Jorge Aparicio "] +edition = "2018" +name = "homogeneous" +# this crate is only used for testing +publish = false +version = "0.0.0-alpha.0" + +[dependencies] +bare-metal = "0.2.4" + +[dependencies.cortex-m-rtfm] +path = ".." +features = ["homogeneous"] + +[dev-dependencies] +panic-halt = "0.2.0" diff --git a/homogeneous/README.md b/homogeneous/README.md new file mode 100644 index 0000000..17e9c6e --- /dev/null +++ b/homogeneous/README.md @@ -0,0 +1 @@ +This directory contains *homogeneous* multi-core compile pass tests. diff --git a/homogeneous/examples/smallest.rs b/homogeneous/examples/smallest.rs new file mode 100644 index 0000000..b99476c --- /dev/null +++ b/homogeneous/examples/smallest.rs @@ -0,0 +1,7 @@ +#![no_main] +#![no_std] + +use panic_halt as _; + +#[rtfm::app(cores = 2, device = homogeneous)] +const APP: () = {}; diff --git a/homogeneous/examples/x-init-2.rs b/homogeneous/examples/x-init-2.rs new file mode 100644 index 0000000..f51e2f6 --- /dev/null +++ b/homogeneous/examples/x-init-2.rs @@ -0,0 +1,39 @@ +//! [compile-pass] Cross initialization of late resources + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_halt as _; + +#[rtfm::app(cores = 2, device = homogeneous)] +const APP: () = { + extern "C" { + // owned by core #1 but initialized by core #0 + static mut X: u32; + + // owned by core #0 but initialized by core #1 + static mut Y: u32; + } + + #[init(core = 0, late = [X])] + fn a(_: a::Context) -> a::LateResources { + a::LateResources { X: 0 } + } + + #[idle(core = 0, resources = [Y])] + fn b(_: b::Context) -> ! { + loop {} + } + + #[init(core = 1)] + fn c(_: c::Context) -> c::LateResources { + c::LateResources { Y: 0 } + } + + #[idle(core = 1, resources = [X])] + fn d(_: d::Context) -> ! { + loop {} + } +}; diff --git a/homogeneous/examples/x-init.rs b/homogeneous/examples/x-init.rs new file mode 100644 index 0000000..5089e38 --- /dev/null +++ b/homogeneous/examples/x-init.rs @@ -0,0 +1,26 @@ +//! [compile-pass] Split initialization of late resources + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_halt as _; + +#[rtfm::app(cores = 2, device = homogeneous)] +const APP: () = { + extern "C" { + static mut X: u32; + static mut Y: u32; + } + + #[init(core = 0, late = [X])] + fn a(_: a::Context) -> a::LateResources { + a::LateResources { X: 0 } + } + + #[init(core = 1)] + fn b(_: b::Context) -> b::LateResources { + b::LateResources { Y: 0 } + } +}; diff --git a/homogeneous/examples/x-schedule.rs b/homogeneous/examples/x-schedule.rs new file mode 100644 index 0000000..12b5cb8 --- /dev/null +++ b/homogeneous/examples/x-schedule.rs @@ -0,0 +1,36 @@ +#![no_main] +#![no_std] + +use panic_halt as _; + +#[rtfm::app(cores = 2, device = homogeneous, monotonic = homogeneous::MT)] +const APP: () = { + #[init(core = 0, spawn = [ping])] + fn init(c: init::Context) { + c.spawn.ping().ok(); + } + + #[task(core = 0, schedule = [ping])] + fn pong(c: pong::Context) { + c.schedule.ping(c.scheduled + 1_000_000).ok(); + } + + #[task(core = 1, schedule = [pong])] + fn ping(c: ping::Context) { + c.schedule.pong(c.scheduled + 1_000_000).ok(); + } + + extern "C" { + #[core = 0] + fn I0(); + + #[core = 0] + fn I1(); + + #[core = 1] + fn I0(); + + #[core = 1] + fn I1(); + } +}; diff --git a/homogeneous/examples/x-spawn.rs b/homogeneous/examples/x-spawn.rs new file mode 100644 index 0000000..a76ac61 --- /dev/null +++ b/homogeneous/examples/x-spawn.rs @@ -0,0 +1,20 @@ +#![no_main] +#![no_std] + +use panic_halt as _; + +#[rtfm::app(cores = 2, device = homogeneous)] +const APP: () = { + #[init(core = 0, spawn = [foo])] + fn init(c: init::Context) { + c.spawn.foo().ok(); + } + + #[task(core = 1)] + fn foo(_: foo::Context) {} + + extern "C" { + #[core = 1] + fn I0(); + } +}; diff --git a/homogeneous/src/lib.rs b/homogeneous/src/lib.rs new file mode 100644 index 0000000..a4f0ec5 --- /dev/null +++ b/homogeneous/src/lib.rs @@ -0,0 +1,94 @@ +//! Fake multi-core PAC + +#![no_std] + +use core::{ + cmp::Ordering, + ops::{Add, Sub}, +}; + +use bare_metal::Nr; +use rtfm::Monotonic; + +// both cores have the exact same interrupts +pub use Interrupt_0 as Interrupt_1; + +// Fake priority bits +pub const NVIC_PRIO_BITS: u8 = 3; + +pub fn xpend(_core: u8, _interrupt: impl Nr) {} + +/// Fake monotonic timer +pub struct MT; + +unsafe impl Monotonic for MT { + type Instant = Instant; + + fn ratio() -> u32 { + 1 + } + + unsafe fn reset() { + (0xE0001004 as *mut u32).write_volatile(0) + } + + fn now() -> Instant { + unsafe { Instant((0xE0001004 as *const u32).read_volatile() as i32) } + } + + fn zero() -> Instant { + Instant(0) + } +} + +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct Instant(i32); + +impl Add for Instant { + type Output = Instant; + + fn add(self, rhs: u32) -> Self { + Instant(self.0.wrapping_add(rhs as i32)) + } +} + +impl Sub for Instant { + type Output = u32; + + fn sub(self, rhs: Self) -> u32 { + self.0.checked_sub(rhs.0).unwrap() as u32 + } +} + +impl Ord for Instant { + fn cmp(&self, rhs: &Self) -> Ordering { + self.0.wrapping_sub(rhs.0).cmp(&0) + } +} + +impl PartialOrd for Instant { + fn partial_cmp(&self, rhs: &Self) -> Option { + Some(self.cmp(rhs)) + } +} + +// Fake interrupts +#[allow(non_camel_case_types)] +#[derive(Clone, Copy)] +#[repr(u8)] +pub enum Interrupt_0 { + I0 = 0, + I1 = 1, + I2 = 2, + I3 = 3, + I4 = 4, + I5 = 5, + I6 = 6, + I7 = 7, +} + +unsafe impl Nr for Interrupt_0 { + fn nr(&self) -> u8 { + *self as u8 + } +} diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 2854dad..c4e897f 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -24,3 +24,4 @@ git = "https://github.com/japaric/rtfm-syntax" [features] heterogeneous = [] +homogeneous = [] diff --git a/macros/src/check.rs b/macros/src/check.rs index c22a0f1..619ec8f 100644 --- a/macros/src/check.rs +++ b/macros/src/check.rs @@ -20,6 +20,28 @@ impl<'a> Extra<'a> { } pub fn app<'a>(app: &'a App, analysis: &Analysis) -> parse::Result> { + if cfg!(feature = "homogeneous") { + // this RTFM mode uses the same namespace for all cores so we need to check that the + // identifiers used for each core `#[init]` and `#[idle]` functions don't collide + let mut seen = HashSet::new(); + + for name in app + .inits + .values() + .map(|init| &init.name) + .chain(app.idles.values().map(|idle| &idle.name)) + { + if seen.contains(name) { + return Err(parse::Error::new( + name.span(), + "this identifier is already being used by another core", + )); + } else { + seen.insert(name); + } + } + } + // check that all exceptions are valid; only exceptions with configurable priorities are // accepted for (name, task) in app diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs index 86b4a67..9276626 100644 --- a/macros/src/codegen.rs +++ b/macros/src/codegen.rs @@ -67,10 +67,11 @@ pub fn app(app: &App, analysis: &Analysis, extra: &Extra) -> TokenStream2 { )); let cfg_core = util::cfg_core(core, app.args.cores); + let main = util::suffixed("main", core); mains.push(quote!( #[no_mangle] #cfg_core - unsafe fn main() -> ! { + unsafe extern "C" fn #main() -> ! { #(#assertion_stmts)* #(#pre_init_stmts)* diff --git a/macros/src/codegen/dispatchers.rs b/macros/src/codegen/dispatchers.rs index 65d25c7..988e3c8 100644 --- a/macros/src/codegen/dispatchers.rs +++ b/macros/src/codegen/dispatchers.rs @@ -55,8 +55,14 @@ pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec), quote!(rtfm::export::Queue(rtfm::export::iQueue::u8())), ) @@ -156,7 +162,7 @@ pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec util::cfg_core(*core, app.args.cores), // shared `static`s and cross-initialized resources need to be in `.shared` memory - _ => Some(quote!(#[rtfm::export::shared])), + _ => { + if cfg!(feature = "heterogeneous") { + Some(quote!(#[rtfm::export::shared])) + } else { + None + } + } }; let (ty, expr) = if let Some(expr) = expr { diff --git a/macros/src/codegen/software_tasks.rs b/macros/src/codegen/software_tasks.rs index 8b2c0cd..383a5d8 100644 --- a/macros/src/codegen/software_tasks.rs +++ b/macros/src/codegen/software_tasks.rs @@ -52,8 +52,14 @@ pub fn codegen( })), ) } else { + let shared = if cfg!(feature = "heterogeneous") { + Some(quote!(#[rtfm::export::shared])) + } else { + None + }; + ( - Some(quote!(#[rtfm::export::shared])), + shared, quote!(rtfm::export::MCFQ<#cap_ty>), quote!(rtfm::export::Queue(rtfm::export::iQueue::u8())), ) diff --git a/macros/src/codegen/spawn_body.rs b/macros/src/codegen/spawn_body.rs index 83cb5c0..98bce07 100644 --- a/macros/src/codegen/spawn_body.rs +++ b/macros/src/codegen/spawn_body.rs @@ -45,14 +45,15 @@ pub fn codegen( }; let device = extra.device; + let enum_ = util::interrupt_ident(receiver, app.args.cores); let interrupt = &analysis.interrupts[&receiver][&priority]; let pend = if sender != receiver { quote!( - #device::xpend(#receiver, #device::Interrupt::#interrupt); + #device::xpend(#receiver, #device::#enum_::#interrupt); ) } else { quote!( - rtfm::pend(#device::Interrupt::#interrupt); + rtfm::pend(#device::#enum_::#interrupt); ) }; diff --git a/macros/src/codegen/timer_queue.rs b/macros/src/codegen/timer_queue.rs index cb84577..d306ed5 100644 --- a/macros/src/codegen/timer_queue.rs +++ b/macros/src/codegen/timer_queue.rs @@ -89,15 +89,16 @@ pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec Vec>(); let priority = timer_queue.priority; + let sys_tick = util::suffixed("SysTick", sender); items.push(quote!( #cfg_sender #[no_mangle] - unsafe fn SysTick() { + unsafe fn #sys_tick() { use rtfm::Mutex as _; /// The priority of this handler diff --git a/macros/src/codegen/util.rs b/macros/src/codegen/util.rs index 203fcee..8c43b35 100644 --- a/macros/src/codegen/util.rs +++ b/macros/src/codegen/util.rs @@ -27,9 +27,11 @@ pub fn capacity_typenum(capacity: u8, round_up_to_power_of_two: bool) -> TokenSt pub fn cfg_core(core: Core, cores: u8) -> Option { if cores == 1 { None - } else { + } else if cfg!(feature = "heterogeneous") { let core = core.to_string(); Some(quote!(#[cfg(core = #core)])) + } else { + None } } @@ -102,6 +104,15 @@ pub fn instants_ident(task: &Ident, sender: Core) -> Ident { Ident::new(&format!("{}_S{}_INSTANTS", task, sender), Span::call_site()) } +pub fn interrupt_ident(core: Core, cores: u8) -> Ident { + let span = Span::call_site(); + if cores == 1 { + Ident::new("Interrupt", span) + } else { + Ident::new(&format!("Interrupt_{}", core), span) + } +} + /// Generates a pre-reexport identifier for the "late resources" struct pub fn late_resources_ident(init: &Ident) -> Ident { Ident::new( @@ -245,6 +256,16 @@ pub fn spawn_t_ident(receiver: Core, priority: u8, sender: Core) -> Ident { ) } +pub fn suffixed(name: &str, core: u8) -> Ident { + let span = Span::call_site(); + + if cfg!(feature = "homogeneous") { + Ident::new(&format!("{}_{}", name, core), span) + } else { + Ident::new(name, span) + } +} + /// Generates an identifier for a timer queue /// /// At most there's one timer queue per core diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 6e1a797..6502d9c 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -20,7 +20,7 @@ pub fn app(args: TokenStream, input: TokenStream) -> TokenStream { args, input, Settings { - parse_cores: cfg!(feature = "heterogeneous"), + parse_cores: cfg!(feature = "heterogeneous") || cfg!(feature = "homogeneous"), parse_exception: true, parse_extern_interrupt: true, parse_interrupt: true, diff --git a/mc/Cargo.toml b/mc/Cargo.toml deleted file mode 100644 index 7c75335..0000000 --- a/mc/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -authors = ["Jorge Aparicio "] -edition = "2018" -name = "mc" -# this crate is only used for testing -publish = false -version = "0.0.0-alpha.0" - -[dependencies] -cortex-m = "0.6.0" - -[dependencies.cortex-m-rtfm] -path = ".." -features = ["heterogeneous"] - -[dev-dependencies] -panic-halt = "0.2.0" -microamp = "0.1.0-alpha.1" diff --git a/mc/README.md b/mc/README.md deleted file mode 100644 index e1335bb..0000000 --- a/mc/README.md +++ /dev/null @@ -1 +0,0 @@ -This directory contains multi-core compile pass tests. diff --git a/mc/examples/smallest.rs b/mc/examples/smallest.rs deleted file mode 100644 index 792935a..0000000 --- a/mc/examples/smallest.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![no_main] -#![no_std] - -use panic_halt as _; - -#[rtfm::app(cores = 2, device = mc)] -const APP: () = {}; diff --git a/mc/examples/x-init-2.rs b/mc/examples/x-init-2.rs deleted file mode 100644 index ff48b11..0000000 --- a/mc/examples/x-init-2.rs +++ /dev/null @@ -1,39 +0,0 @@ -//! [compile-pass] Cross initialization of late resources - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_halt as _; - -#[rtfm::app(cores = 2, device = mc)] -const APP: () = { - extern "C" { - // owned by core #1 but initialized by core #0 - static mut X: u32; - - // owned by core #0 but initialized by core #1 - static mut Y: u32; - } - - #[init(core = 0, late = [X])] - fn a(_: a::Context) -> a::LateResources { - a::LateResources { X: 0 } - } - - #[idle(core = 0, resources = [Y])] - fn b(_: b::Context) -> ! { - loop {} - } - - #[init(core = 1)] - fn c(_: c::Context) -> c::LateResources { - c::LateResources { Y: 0 } - } - - #[idle(core = 1, resources = [X])] - fn d(_: d::Context) -> ! { - loop {} - } -}; diff --git a/mc/examples/x-init.rs b/mc/examples/x-init.rs deleted file mode 100644 index 3f26c5c..0000000 --- a/mc/examples/x-init.rs +++ /dev/null @@ -1,26 +0,0 @@ -//! [compile-pass] Split initialization of late resources - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_halt as _; - -#[rtfm::app(cores = 2, device = mc)] -const APP: () = { - extern "C" { - static mut X: u32; - static mut Y: u32; - } - - #[init(core = 0, late = [X])] - fn a(_: a::Context) -> a::LateResources { - a::LateResources { X: 0 } - } - - #[init(core = 1)] - fn b(_: b::Context) -> b::LateResources { - b::LateResources { Y: 0 } - } -}; diff --git a/mc/examples/x-schedule.rs b/mc/examples/x-schedule.rs deleted file mode 100644 index 76e70ac..0000000 --- a/mc/examples/x-schedule.rs +++ /dev/null @@ -1,36 +0,0 @@ -#![no_main] -#![no_std] - -use panic_halt as _; - -#[rtfm::app(cores = 2, device = mc, monotonic = mc::MT)] -const APP: () = { - #[init(core = 0, spawn = [ping])] - fn init(c: init::Context) { - c.spawn.ping().ok(); - } - - #[task(core = 0, schedule = [ping])] - fn pong(c: pong::Context) { - c.schedule.ping(c.scheduled + 1_000_000).ok(); - } - - #[task(core = 1, schedule = [pong])] - fn ping(c: ping::Context) { - c.schedule.pong(c.scheduled + 1_000_000).ok(); - } - - extern "C" { - #[core = 0] - fn I0(); - - #[core = 0] - fn I1(); - - #[core = 1] - fn I0(); - - #[core = 1] - fn I1(); - } -}; diff --git a/mc/examples/x-spawn.rs b/mc/examples/x-spawn.rs deleted file mode 100644 index 749918f..0000000 --- a/mc/examples/x-spawn.rs +++ /dev/null @@ -1,20 +0,0 @@ -#![no_main] -#![no_std] - -use panic_halt as _; - -#[rtfm::app(cores = 2, device = mc)] -const APP: () = { - #[init(core = 0, spawn = [foo])] - fn init(c: init::Context) { - c.spawn.foo().ok(); - } - - #[task(core = 1)] - fn foo(_: foo::Context) {} - - extern "C" { - #[core = 1] - fn I0(); - } -}; diff --git a/mc/src/lib.rs b/mc/src/lib.rs deleted file mode 100644 index d86c0e8..0000000 --- a/mc/src/lib.rs +++ /dev/null @@ -1,99 +0,0 @@ -//! Fake multi-core PAC - -#![no_std] - -use core::{ - cmp::Ordering, - ops::{Add, Sub}, -}; - -use cortex_m::interrupt::Nr; -use rtfm::Monotonic; - -// Fake priority bits -pub const NVIC_PRIO_BITS: u8 = 3; - -pub struct CrossPend; - -pub fn xpend(_core: u8, _interrupt: impl Nr) {} - -/// Fake monotonic timer -pub struct MT; - -unsafe impl Monotonic for MT { - type Instant = Instant; - - fn ratio() -> u32 { - 1 - } - - unsafe fn reset() { - (0xE0001004 as *mut u32).write_volatile(0) - } - - fn now() -> Instant { - unsafe { Instant((0xE0001004 as *const u32).read_volatile() as i32) } - } - - fn zero() -> Instant { - Instant(0) - } -} - -#[derive(Clone, Copy, Eq, PartialEq)] -pub struct Instant(i32); - -impl Add for Instant { - type Output = Instant; - - fn add(self, rhs: u32) -> Self { - Instant(self.0.wrapping_add(rhs as i32)) - } -} - -impl Sub for Instant { - type Output = u32; - - fn sub(self, rhs: Self) -> u32 { - self.0.checked_sub(rhs.0).unwrap() as u32 - } -} - -impl Ord for Instant { - fn cmp(&self, rhs: &Self) -> Ordering { - self.0.wrapping_sub(rhs.0).cmp(&0) - } -} - -impl PartialOrd for Instant { - fn partial_cmp(&self, rhs: &Self) -> Option { - Some(self.cmp(rhs)) - } -} - -// Fake interrupts -pub enum Interrupt { - I0, - I1, - I2, - I3, - I4, - I5, - I6, - I7, -} - -unsafe impl Nr for Interrupt { - fn nr(&self) -> u8 { - match self { - Interrupt::I0 => 0, - Interrupt::I1 => 1, - Interrupt::I2 => 2, - Interrupt::I3 => 3, - Interrupt::I4 => 4, - Interrupt::I5 => 5, - Interrupt::I6 => 6, - Interrupt::I7 => 7, - } - } -} diff --git a/src/lib.rs b/src/lib.rs index 73e6e20..acb3a63 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,7 +47,7 @@ use cortex_m::{ interrupt::Nr, peripheral::{CBP, CPUID, DCB, DWT, FPB, FPU, ITM, MPU, NVIC, SCB, TPIU}, }; -#[cfg(not(feature = "heterogeneous"))] +#[cfg(all(not(feature = "heterogeneous"), not(feature = "homogeneous")))] use cortex_m_rt as _; // vector table pub use cortex_m_rtfm_macros::app; pub use rtfm_core::{Exclusive, Mutex}; -- cgit v1.2.3 From 4e51bb68b976c6bb6a9a989dc560d2a8123a84ca Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Thu, 20 Jun 2019 06:19:59 +0200 Subject: RFC #207 --- examples/baseline.rs | 4 ++-- examples/binds.rs | 2 +- examples/capacity.rs | 4 ++-- examples/generics.rs | 8 ++++---- examples/interrupt.rs | 4 ++-- examples/late.rs | 4 ++-- examples/lock.rs | 12 +++++------ examples/pool.rs | 4 ++-- examples/resource.rs | 8 ++++---- examples/shared-with-init.rs | 4 ++-- examples/static.rs | 8 ++++---- examples/t-binds.rs | 6 ++++-- examples/t-resource.rs | 8 ++++---- examples/t-schedule.rs | 8 ++++---- examples/t-spawn.rs | 8 ++++---- examples/types.rs | 16 +++++++-------- macros/src/check.rs | 19 ++++++----------- macros/src/codegen/hardware_tasks.rs | 4 ++-- macros/src/codegen/pre_init.rs | 14 ++++++------- macros/src/codegen/util.rs | 12 +++++++++++ macros/src/lib.rs | 21 ++++++++----------- macros/src/tests/multi.rs | 12 +++++------ macros/src/tests/single.rs | 7 +++---- ui/single/exception-invalid.rs | 4 ++-- ui/single/exception-invalid.stderr | 4 ++-- ui/single/exception-systick-used.rs | 4 ++-- ui/single/exception-systick-used.stderr | 4 ++-- ui/single/extern-interrupt-used.rs | 2 +- ui/single/extern-interrupt-used.stderr | 6 +++--- ui/single/locals-cfg.rs | 8 ++++---- ui/single/resources-cfg.rs | 8 ++++---- ui/single/resources-cfg.stderr | 12 +++++------ ui/single/task-priority-too-high.rs | 36 ++++++++++++++++----------------- 33 files changed, 143 insertions(+), 142 deletions(-) (limited to 'macros') diff --git a/examples/baseline.rs b/examples/baseline.rs index cc9b412..3a8ab0e 100644 --- a/examples/baseline.rs +++ b/examples/baseline.rs @@ -35,8 +35,8 @@ const APP: () = { } } - #[interrupt(spawn = [foo])] - fn UART0(c: UART0::Context) { + #[task(binds = UART0, spawn = [foo])] + fn uart0(c: uart0::Context) { hprintln!("UART0(baseline = {:?})", c.start).unwrap(); // `foo` inherits the baseline of `UART0`: its `start` time diff --git a/examples/binds.rs b/examples/binds.rs index 1959d75..b10cb43 100644 --- a/examples/binds.rs +++ b/examples/binds.rs @@ -30,7 +30,7 @@ const APP: () = { loop {} } - #[interrupt(binds = UART0)] + #[task(binds = UART0)] fn foo(_: foo::Context) { static mut TIMES: u32 = 0; diff --git a/examples/capacity.rs b/examples/capacity.rs index e1a835c..ebc86b8 100644 --- a/examples/capacity.rs +++ b/examples/capacity.rs @@ -16,8 +16,8 @@ const APP: () = { rtfm::pend(Interrupt::UART0); } - #[interrupt(spawn = [foo, bar])] - fn UART0(c: UART0::Context) { + #[task(binds = UART0, spawn = [foo, bar])] + fn uart0(c: uart0::Context) { c.spawn.foo(0).unwrap(); c.spawn.foo(1).unwrap(); c.spawn.foo(2).unwrap(); diff --git a/examples/generics.rs b/examples/generics.rs index a35ba23..562470d 100644 --- a/examples/generics.rs +++ b/examples/generics.rs @@ -20,8 +20,8 @@ const APP: () = { rtfm::pend(Interrupt::UART1); } - #[interrupt(resources = [SHARED])] - fn UART0(c: UART0::Context) { + #[task(binds = UART0, resources = [SHARED])] + fn uart0(c: uart0::Context) { static mut STATE: u32 = 0; hprintln!("UART0(STATE = {})", *STATE).unwrap(); @@ -33,8 +33,8 @@ const APP: () = { debug::exit(debug::EXIT_SUCCESS); } - #[interrupt(priority = 2, resources = [SHARED])] - fn UART1(c: UART1::Context) { + #[task(binds = UART1, priority = 2, resources = [SHARED])] + fn uart1(c: uart1::Context) { static mut STATE: u32 = 0; hprintln!("UART1(STATE = {})", *STATE).unwrap(); diff --git a/examples/interrupt.rs b/examples/interrupt.rs index 3fe8ff3..f0069b8 100644 --- a/examples/interrupt.rs +++ b/examples/interrupt.rs @@ -33,8 +33,8 @@ const APP: () = { loop {} } - #[interrupt] - fn UART0(_: UART0::Context) { + #[task(binds = UART0)] + fn uart0(_: uart0::Context) { static mut TIMES: u32 = 0; // Safe access to local `static mut` variable diff --git a/examples/late.rs b/examples/late.rs index 4d48a6a..19807ff 100644 --- a/examples/late.rs +++ b/examples/late.rs @@ -47,8 +47,8 @@ const APP: () = { } } - #[interrupt(resources = [P])] - fn UART0(c: UART0::Context) { + #[task(binds = UART0, resources = [P])] + fn uart0(c: uart0::Context) { c.resources.P.enqueue(42).unwrap(); } }; diff --git a/examples/lock.rs b/examples/lock.rs index b7d36b4..17b6a58 100644 --- a/examples/lock.rs +++ b/examples/lock.rs @@ -19,8 +19,8 @@ const APP: () = { } // when omitted priority is assumed to be `1` - #[interrupt(resources = [SHARED])] - fn GPIOA(mut c: GPIOA::Context) { + #[task(binds = GPIOA, resources = [SHARED])] + fn gpioa(mut c: gpioa::Context) { hprintln!("A").unwrap(); // the lower priority task requires a critical section to access the data @@ -44,16 +44,16 @@ const APP: () = { debug::exit(debug::EXIT_SUCCESS); } - #[interrupt(priority = 2, resources = [SHARED])] - fn GPIOB(c: GPIOB::Context) { + #[task(binds = GPIOB, priority = 2, resources = [SHARED])] + fn gpiob(c: gpiob::Context) { // the higher priority task does *not* need a critical section *c.resources.SHARED += 1; hprintln!("D - SHARED = {}", *c.resources.SHARED).unwrap(); } - #[interrupt(priority = 3)] - fn GPIOC(_: GPIOC::Context) { + #[task(binds = GPIOC, priority = 3)] + fn gpioc(_: gpioc::Context) { hprintln!("C").unwrap(); } }; diff --git a/examples/pool.rs b/examples/pool.rs index db321b5..8c44cb1 100644 --- a/examples/pool.rs +++ b/examples/pool.rs @@ -29,8 +29,8 @@ const APP: () = { rtfm::pend(Interrupt::I2C0); } - #[interrupt(priority = 2, spawn = [foo, bar])] - fn I2C0(c: I2C0::Context) { + #[task(binds = I2C0, priority = 2, spawn = [foo, bar])] + fn i2c0(c: i2c0::Context) { // claim a memory block, leave it uninitialized and .. let x = P::alloc().unwrap().freeze(); diff --git a/examples/resource.rs b/examples/resource.rs index 8268950..661f8c3 100644 --- a/examples/resource.rs +++ b/examples/resource.rs @@ -31,16 +31,16 @@ const APP: () = { } // `SHARED` can be access from this context - #[interrupt(resources = [SHARED])] - fn UART0(c: UART0::Context) { + #[task(binds = UART0, resources = [SHARED])] + fn uart0(c: uart0::Context) { *c.resources.SHARED += 1; hprintln!("UART0: SHARED = {}", c.resources.SHARED).unwrap(); } // `SHARED` can be access from this context - #[interrupt(resources = [SHARED])] - fn UART1(c: UART1::Context) { + #[task(binds = UART1, resources = [SHARED])] + fn uart1(c: uart1::Context) { *c.resources.SHARED += 1; hprintln!("UART1: SHARED = {}", c.resources.SHARED).unwrap(); diff --git a/examples/shared-with-init.rs b/examples/shared-with-init.rs index 1640ca9..ed73c8b 100644 --- a/examples/shared-with-init.rs +++ b/examples/shared-with-init.rs @@ -25,8 +25,8 @@ const APP: () = { rtfm::pend(Interrupt::UART0); } - #[interrupt(resources = [SHARED])] - fn UART0(c: UART0::Context) { + #[task(binds = UART0, resources = [SHARED])] + fn uart0(c: uart0::Context) { if let Some(message) = c.resources.SHARED.take() { // `message` has been received drop(message); diff --git a/examples/static.rs b/examples/static.rs index eeb522f..5eb7b19 100644 --- a/examples/static.rs +++ b/examples/static.rs @@ -23,15 +23,15 @@ const APP: () = { init::LateResources { KEY: 0xdeadbeef } } - #[interrupt(resources = [KEY])] - fn UART0(c: UART0::Context) { + #[task(binds = UART0, resources = [KEY])] + fn uart0(c: uart0::Context) { hprintln!("UART0(KEY = {:#x})", c.resources.KEY).unwrap(); debug::exit(debug::EXIT_SUCCESS); } - #[interrupt(priority = 2, resources = [KEY])] - fn UART1(c: UART1::Context) { + #[task(binds = UART1, priority = 2, resources = [KEY])] + fn uart1(c: uart1::Context) { hprintln!("UART1(KEY = {:#x})", c.resources.KEY).unwrap(); } }; diff --git a/examples/t-binds.rs b/examples/t-binds.rs index b4693a4..dda8e20 100644 --- a/examples/t-binds.rs +++ b/examples/t-binds.rs @@ -12,12 +12,14 @@ const APP: () = { #[init] fn init(_: init::Context) {} - #[exception(binds = SVCall)] + // Cortex-M exception + #[task(binds = SVCall)] fn foo(c: foo::Context) { foo_trampoline(c) } - #[interrupt(binds = UART0)] + // LM3S6965 interrupt + #[task(binds = UART0)] fn bar(c: bar::Context) { bar_trampoline(c) } diff --git a/examples/t-resource.rs b/examples/t-resource.rs index 40dc2a6..adcc04b 100644 --- a/examples/t-resource.rs +++ b/examples/t-resource.rs @@ -51,8 +51,8 @@ const APP: () = { loop {} } - #[interrupt(resources = [O3, S1, S2, S3])] - fn UART0(c: UART0::Context) { + #[task(binds = UART0, resources = [O3, S1, S2, S3])] + fn uart0(c: uart0::Context) { // owned by interrupt == `&mut` let _: &mut u32 = c.resources.O3; @@ -66,8 +66,8 @@ const APP: () = { let _: &u32 = c.resources.S3; } - #[interrupt(resources = [S2, O5])] - fn UART1(c: UART1::Context) { + #[task(binds = UART1, resources = [S2, O5])] + fn uart1(c: uart1::Context) { // owned by interrupt == `&` if read-only let _: &u32 = c.resources.O5; diff --git a/examples/t-schedule.rs b/examples/t-schedule.rs index 67ff358..e6035b3 100644 --- a/examples/t-schedule.rs +++ b/examples/t-schedule.rs @@ -26,15 +26,15 @@ const APP: () = { loop {} } - #[exception(schedule = [foo, bar, baz])] - fn SVCall(c: SVCall::Context) { + #[task(binds = SVCall, schedule = [foo, bar, baz])] + fn svcall(c: svcall::Context) { let _: Result<(), ()> = c.schedule.foo(c.start + 70.cycles()); let _: Result<(), u32> = c.schedule.bar(c.start + 80.cycles(), 0); let _: Result<(), (u32, u32)> = c.schedule.baz(c.start + 90.cycles(), 0, 1); } - #[interrupt(schedule = [foo, bar, baz])] - fn UART0(c: UART0::Context) { + #[task(binds = UART0, schedule = [foo, bar, baz])] + fn uart0(c: uart0::Context) { let _: Result<(), ()> = c.schedule.foo(c.start + 100.cycles()); let _: Result<(), u32> = c.schedule.bar(c.start + 110.cycles(), 0); let _: Result<(), (u32, u32)> = c.schedule.baz(c.start + 120.cycles(), 0, 1); diff --git a/examples/t-spawn.rs b/examples/t-spawn.rs index 6bb9b31..682b9b8 100644 --- a/examples/t-spawn.rs +++ b/examples/t-spawn.rs @@ -25,15 +25,15 @@ const APP: () = { loop {} } - #[exception(spawn = [foo, bar, baz])] - fn SVCall(c: SVCall::Context) { + #[task(binds = SVCall, spawn = [foo, bar, baz])] + fn svcall(c: svcall::Context) { let _: Result<(), ()> = c.spawn.foo(); let _: Result<(), u32> = c.spawn.bar(0); let _: Result<(), (u32, u32)> = c.spawn.baz(0, 1); } - #[interrupt(spawn = [foo, bar, baz])] - fn UART0(c: UART0::Context) { + #[task(binds = UART0, spawn = [foo, bar, baz])] + fn uart0(c: uart0::Context) { let _: Result<(), ()> = c.spawn.foo(); let _: Result<(), u32> = c.spawn.bar(0); let _: Result<(), (u32, u32)> = c.spawn.baz(0, 1); diff --git a/examples/types.rs b/examples/types.rs index 2e72f0a..3e9c7ea 100644 --- a/examples/types.rs +++ b/examples/types.rs @@ -24,19 +24,19 @@ const APP: () = { debug::exit(debug::EXIT_SUCCESS); } - #[exception(schedule = [foo], spawn = [foo])] - fn SVCall(c: SVCall::Context) { + #[task(binds = SVCall, schedule = [foo], spawn = [foo])] + fn svcall(c: svcall::Context) { let _: Instant = c.start; - let _: SVCall::Schedule = c.schedule; - let _: SVCall::Spawn = c.spawn; + let _: svcall::Schedule = c.schedule; + let _: svcall::Spawn = c.spawn; } - #[interrupt(resources = [SHARED], schedule = [foo], spawn = [foo])] - fn UART0(c: UART0::Context) { + #[task(binds = UART0, resources = [SHARED], schedule = [foo], spawn = [foo])] + fn uart0(c: uart0::Context) { let _: Instant = c.start; let _: resources::SHARED = c.resources.SHARED; - let _: UART0::Schedule = c.schedule; - let _: UART0::Spawn = c.spawn; + let _: uart0::Schedule = c.schedule; + let _: uart0::Spawn = c.spawn; } #[task(priority = 2, resources = [SHARED], schedule = [foo], spawn = [foo])] diff --git a/macros/src/check.rs b/macros/src/check.rs index 619ec8f..85fda75 100644 --- a/macros/src/check.rs +++ b/macros/src/check.rs @@ -3,7 +3,7 @@ use std::collections::HashSet; use proc_macro2::Span; use rtfm_syntax::{ analyze::Analysis, - ast::{App, CustomArg, HardwareTaskKind}, + ast::{App, CustomArg}, }; use syn::{parse, Path}; @@ -44,18 +44,9 @@ pub fn app<'a>(app: &'a App, analysis: &Analysis) -> parse::Result> { // check that all exceptions are valid; only exceptions with configurable priorities are // accepted - for (name, task) in app - .hardware_tasks - .iter() - .filter(|(_, task)| task.kind == HardwareTaskKind::Exception) - { - let name_s = task.args.binds(name).to_string(); + for (name, task) in &app.hardware_tasks { + let name_s = task.args.binds.to_string(); match &*name_s { - // NOTE that some of these don't exist on ARMv6-M but we don't check that here -- the - // code we generate will check that the exception actually exists on ARMv6-M - "MemoryManagement" | "BusFault" | "UsageFault" | "SecureFault" | "SVCall" - | "DebugMonitor" | "PendSV" => {} // OK - "SysTick" => { if analysis.timer_queues.get(&task.args.core).is_some() { return Err(parse::Error::new( @@ -67,12 +58,14 @@ pub fn app<'a>(app: &'a App, analysis: &Analysis) -> parse::Result> { } } - _ => { + "NonMaskableInt" | "HardFault" => { return Err(parse::Error::new( name.span(), "only exceptions with configurable priority can be used as hardware tasks", )); } + + _ => {} } } diff --git a/macros/src/codegen/hardware_tasks.rs b/macros/src/codegen/hardware_tasks.rs index e7f053d..a7af510 100644 --- a/macros/src/codegen/hardware_tasks.rs +++ b/macros/src/codegen/hardware_tasks.rs @@ -50,9 +50,9 @@ pub fn codegen( }; let symbol = if cfg!(feature = "homogeneous") { - util::suffixed(&task.args.binds(name).to_string(), core) + util::suffixed(&task.args.binds.to_string(), core) } else { - task.args.binds(name).clone() + task.args.binds.clone() }; let priority = task.args.priority; diff --git a/macros/src/codegen/pre_init.rs b/macros/src/codegen/pre_init.rs index 19fc646..948dae5 100644 --- a/macros/src/codegen/pre_init.rs +++ b/macros/src/codegen/pre_init.rs @@ -1,6 +1,6 @@ use proc_macro2::TokenStream as TokenStream2; use quote::quote; -use rtfm_syntax::ast::{App, HardwareTaskKind}; +use rtfm_syntax::ast::App; use crate::{analyze::Analysis, check::Extra, codegen::util}; @@ -52,9 +52,9 @@ pub fn codegen( .get(&core) .iter() .flat_map(|interrupts| *interrupts) - .chain(app.hardware_tasks.iter().flat_map(|(name, task)| { - if task.kind == HardwareTaskKind::Interrupt { - Some((&task.args.priority, task.args.binds(name))) + .chain(app.hardware_tasks.values().flat_map(|task| { + if !util::is_exception(&task.args.binds) { + Some((&task.args.priority, &task.args.binds)) } else { // we do exceptions in another pass None @@ -102,9 +102,9 @@ pub fn codegen( } // set exception priorities - for (name, priority) in app.hardware_tasks.iter().filter_map(|(name, task)| { - if task.kind == HardwareTaskKind::Exception { - Some((task.args.binds(name), task.args.priority)) + for (name, priority) in app.hardware_tasks.values().filter_map(|task| { + if util::is_exception(&task.args.binds) { + Some((&task.args.binds, task.args.priority)) } else { None } diff --git a/macros/src/codegen/util.rs b/macros/src/codegen/util.rs index 8c43b35..cd01264 100644 --- a/macros/src/codegen/util.rs +++ b/macros/src/codegen/util.rs @@ -113,6 +113,18 @@ pub fn interrupt_ident(core: Core, cores: u8) -> Ident { } } +/// Whether `name` is an exception with configurable priority +pub fn is_exception(name: &Ident) -> bool { + let s = name.to_string(); + + match &*s { + "MemoryManagement" | "BusFault" | "UsageFault" | "SecureFault" | "SVCall" + | "DebugMonitor" | "PendSV" | "SysTick" => true, + + _ => false, + } +} + /// Generates a pre-reexport identifier for the "late resources" struct pub fn late_resources_ident(init: &Ident) -> Ident { Ident::new( diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 6502d9c..ed55095 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -16,19 +16,14 @@ mod tests; #[proc_macro_attribute] pub fn app(args: TokenStream, input: TokenStream) -> TokenStream { - let (app, analysis) = match rtfm_syntax::parse( - args, - input, - Settings { - parse_cores: cfg!(feature = "heterogeneous") || cfg!(feature = "homogeneous"), - parse_exception: true, - parse_extern_interrupt: true, - parse_interrupt: true, - parse_schedule: true, - optimize_priorities: true, - ..Settings::default() - }, - ) { + let mut settings = Settings::default(); + settings.optimize_priorities = true; + settings.parse_binds = true; + settings.parse_cores = cfg!(feature = "heterogeneous") || cfg!(feature = "homogeneous"); + settings.parse_extern_interrupt = true; + settings.parse_schedule = true; + + let (app, analysis) = match rtfm_syntax::parse(args, input, settings) { Err(e) => return e.to_compile_error().into(), Ok(x) => x, }; diff --git a/macros/src/tests/multi.rs b/macros/src/tests/multi.rs index 37fef53..b55c451 100644 --- a/macros/src/tests/multi.rs +++ b/macros/src/tests/multi.rs @@ -3,6 +3,10 @@ use rtfm_syntax::Settings; #[test] fn analyze() { + let mut settings = Settings::default(); + settings.parse_cores = true; + settings.parse_extern_interrupt = true; + let (app, analysis) = rtfm_syntax::parse2( quote!(device = pac, cores = 2), quote!( @@ -35,13 +39,9 @@ fn analyze() { } }; ), - Settings { - parse_cores: true, - parse_extern_interrupt: true, - ..Settings::default() - }, + settings, ) - .unwrap(); + .unwrap(); let analysis = crate::analyze::app(analysis, &app); diff --git a/macros/src/tests/single.rs b/macros/src/tests/single.rs index fb2d430..5d7a8a9 100644 --- a/macros/src/tests/single.rs +++ b/macros/src/tests/single.rs @@ -3,6 +3,8 @@ use rtfm_syntax::Settings; #[test] fn analyze() { + let mut settings = Settings::default(); + settings.parse_extern_interrupt = true; let (app, analysis) = rtfm_syntax::parse2( quote!(device = pac), quote!( @@ -20,10 +22,7 @@ fn analyze() { } }; ), - Settings { - parse_extern_interrupt: true, - ..Settings::default() - }, + settings, ) .unwrap(); diff --git a/ui/single/exception-invalid.rs b/ui/single/exception-invalid.rs index 426cb67..54f5992 100644 --- a/ui/single/exception-invalid.rs +++ b/ui/single/exception-invalid.rs @@ -2,6 +2,6 @@ #[rtfm::app(device = lm3s6965)] const APP: () = { - #[exception] - fn NonMaskableInt(_: NonMaskableInt::Context) {} + #[task(binds = NonMaskableInt)] + fn nmi(_: nmi::Context) {} }; diff --git a/ui/single/exception-invalid.stderr b/ui/single/exception-invalid.stderr index f7fc292..306074b 100644 --- a/ui/single/exception-invalid.stderr +++ b/ui/single/exception-invalid.stderr @@ -1,8 +1,8 @@ error: only exceptions with configurable priority can be used as hardware tasks --> $DIR/exception-invalid.rs:6:8 | -6 | fn NonMaskableInt(_: NonMaskableInt::Context) {} - | ^^^^^^^^^^^^^^ +6 | fn nmi(_: nmi::Context) {} + | ^^^ error: aborting due to previous error diff --git a/ui/single/exception-systick-used.rs b/ui/single/exception-systick-used.rs index d30da1b..1155834 100644 --- a/ui/single/exception-systick-used.rs +++ b/ui/single/exception-systick-used.rs @@ -2,8 +2,8 @@ #[rtfm::app(device = lm3s6965)] const APP: () = { - #[exception] - fn SysTick(_: SysTick::Context) {} + #[task(binds = SysTick)] + fn sys_tick(_: sys_tick::Context) {} #[task(schedule = [foo])] fn foo(_: foo::Context) {} diff --git a/ui/single/exception-systick-used.stderr b/ui/single/exception-systick-used.stderr index 47786c6..e2ccbd3 100644 --- a/ui/single/exception-systick-used.stderr +++ b/ui/single/exception-systick-used.stderr @@ -1,8 +1,8 @@ error: this exception can't be used because it's being used by the runtime --> $DIR/exception-systick-used.rs:6:8 | -6 | fn SysTick(_: SysTick::Context) {} - | ^^^^^^^ +6 | fn sys_tick(_: sys_tick::Context) {} + | ^^^^^^^^ error: aborting due to previous error diff --git a/ui/single/extern-interrupt-used.rs b/ui/single/extern-interrupt-used.rs index 25f34b3..59f3806 100644 --- a/ui/single/extern-interrupt-used.rs +++ b/ui/single/extern-interrupt-used.rs @@ -2,7 +2,7 @@ #[rtfm::app(device = lm3s6965)] const APP: () = { - #[interrupt(binds = UART0)] + #[task(binds = UART0)] fn a(_: a::Context) {} extern "C" { diff --git a/ui/single/extern-interrupt-used.stderr b/ui/single/extern-interrupt-used.stderr index 8707b1d..2e084ca 100644 --- a/ui/single/extern-interrupt-used.stderr +++ b/ui/single/extern-interrupt-used.stderr @@ -1,8 +1,8 @@ error: `extern` interrupts can't be used as hardware tasks - --> $DIR/extern-interrupt-used.rs:5:25 + --> $DIR/extern-interrupt-used.rs:5:20 | -5 | #[interrupt(binds = UART0)] - | ^^^^^ +5 | #[task(binds = UART0)] + | ^^^^^ error: aborting due to previous error diff --git a/ui/single/locals-cfg.rs b/ui/single/locals-cfg.rs index bcce5ca..8761f72 100644 --- a/ui/single/locals-cfg.rs +++ b/ui/single/locals-cfg.rs @@ -20,16 +20,16 @@ const APP: () = { loop {} } - #[exception] - fn SVCall(_: SVCall::Context) { + #[task(binds = SVCall)] + fn svcall(_: svcall::Context) { #[cfg(never)] static mut FOO: u32 = 0; FOO; } - #[interrupt] - fn UART0(_: UART0::Context) { + #[task(binds = UART0)] + fn uart0(_: uart0::Context) { #[cfg(never)] static mut FOO: u32 = 0; diff --git a/ui/single/resources-cfg.rs b/ui/single/resources-cfg.rs index f8c3672..6f608fa 100644 --- a/ui/single/resources-cfg.rs +++ b/ui/single/resources-cfg.rs @@ -41,16 +41,16 @@ const APP: () = { loop {} } - #[interrupt(resources = [O3, S1, S2, S3])] - fn UART0(c: UART0::Context) { + #[task(binds = UART0, resources = [O3, S1, S2, S3])] + fn uart0(c: uart0::Context) { c.resources.O3; c.resources.S1; c.resources.S2; c.resources.S3; } - #[interrupt(resources = [S2, O5])] - fn UART1(c: UART1::Context) { + #[task(binds = UART1, resources = [S2, O5])] + fn uart1(c: uart1::Context) { c.resources.S2; c.resources.O5; } diff --git a/ui/single/resources-cfg.stderr b/ui/single/resources-cfg.stderr index 88c34d2..55e7ee0 100644 --- a/ui/single/resources-cfg.stderr +++ b/ui/single/resources-cfg.stderr @@ -70,7 +70,7 @@ error[E0609]: no field `S3` on type `idleResources<'_>` | = note: available fields are: `__marker__` -error[E0609]: no field `O3` on type `UART0Resources<'_>` +error[E0609]: no field `O3` on type `uart0Resources<'_>` --> $DIR/resources-cfg.rs:46:21 | 46 | c.resources.O3; @@ -78,7 +78,7 @@ error[E0609]: no field `O3` on type `UART0Resources<'_>` | = note: available fields are: `__marker__` -error[E0609]: no field `S1` on type `UART0Resources<'_>` +error[E0609]: no field `S1` on type `uart0Resources<'_>` --> $DIR/resources-cfg.rs:47:21 | 47 | c.resources.S1; @@ -86,7 +86,7 @@ error[E0609]: no field `S1` on type `UART0Resources<'_>` | = note: available fields are: `__marker__` -error[E0609]: no field `S2` on type `UART0Resources<'_>` +error[E0609]: no field `S2` on type `uart0Resources<'_>` --> $DIR/resources-cfg.rs:48:21 | 48 | c.resources.S2; @@ -94,7 +94,7 @@ error[E0609]: no field `S2` on type `UART0Resources<'_>` | = note: available fields are: `__marker__` -error[E0609]: no field `S3` on type `UART0Resources<'_>` +error[E0609]: no field `S3` on type `uart0Resources<'_>` --> $DIR/resources-cfg.rs:49:21 | 49 | c.resources.S3; @@ -102,7 +102,7 @@ error[E0609]: no field `S3` on type `UART0Resources<'_>` | = note: available fields are: `__marker__` -error[E0609]: no field `S2` on type `UART1Resources<'_>` +error[E0609]: no field `S2` on type `uart1Resources<'_>` --> $DIR/resources-cfg.rs:54:21 | 54 | c.resources.S2; @@ -110,7 +110,7 @@ error[E0609]: no field `S2` on type `UART1Resources<'_>` | = note: available fields are: `__marker__` -error[E0609]: no field `O5` on type `UART1Resources<'_>` +error[E0609]: no field `O5` on type `uart1Resources<'_>` --> $DIR/resources-cfg.rs:55:21 | 55 | c.resources.O5; diff --git a/ui/single/task-priority-too-high.rs b/ui/single/task-priority-too-high.rs index c7c9dc9..24cb11e 100644 --- a/ui/single/task-priority-too-high.rs +++ b/ui/single/task-priority-too-high.rs @@ -7,32 +7,32 @@ const APP: () = { #[init] fn init(_: init::Context) {} - #[interrupt(priority = 1)] - fn GPIOA(_: GPIOA::Context) {} + #[task(binds = GPIOA, priority = 1)] + fn gpioa(_: gpioa::Context) {} - #[interrupt(priority = 2)] - fn GPIOB(_: GPIOB::Context) {} + #[task(binds = GPIOB, priority = 2)] + fn gpiob(_: gpiob::Context) {} - #[interrupt(priority = 3)] - fn GPIOC(_: GPIOC::Context) {} + #[task(binds = GPIOC, priority = 3)] + fn gpioc(_: gpioc::Context) {} - #[interrupt(priority = 4)] - fn GPIOD(_: GPIOD::Context) {} + #[task(binds = GPIOD, priority = 4)] + fn gpiod(_: gpiod::Context) {} - #[interrupt(priority = 5)] - fn GPIOE(_: GPIOE::Context) {} + #[task(binds = GPIOE, priority = 5)] + fn gpioe(_: gpioe::Context) {} - #[interrupt(priority = 6)] - fn UART0(_: UART0::Context) {} + #[task(binds = UART0, priority = 6)] + fn uart0(_: uart0::Context) {} - #[interrupt(priority = 7)] - fn UART1(_: UART1::Context) {} + #[task(binds = UART1, priority = 7)] + fn uart1(_: uart1::Context) {} // OK, this is the maximum priority supported by the device - #[interrupt(priority = 8)] - fn SSI0(_: SSI0::Context) {} + #[task(binds = SSI0, priority = 8)] + fn ssi0(_: ssi0::Context) {} // this value is too high! - #[interrupt(priority = 9)] - fn I2C0(_: I2C0::Context) {} + #[task(binds = I2C0, priority = 9)] + fn i2c0(_: i2c0::Context) {} }; -- cgit v1.2.3 From 596cf585ea8dc278d88e0652dffbacbc75de04c6 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Mon, 24 Jun 2019 14:09:12 +0200 Subject: Monotonic trait is safe; add MultiCore trait --- heterogeneous/src/lib.rs | 6 ++++-- homogeneous/src/lib.rs | 6 ++++-- macros/src/codegen.rs | 2 +- macros/src/codegen/assertions.rs | 11 +++++++++-- src/cyccnt.rs | 7 ++++++- src/export.rs | 7 +++++++ src/lib.rs | 5 ++++- 7 files changed, 35 insertions(+), 9 deletions(-) (limited to 'macros') diff --git a/heterogeneous/src/lib.rs b/heterogeneous/src/lib.rs index a4f0ec5..3288bfe 100644 --- a/heterogeneous/src/lib.rs +++ b/heterogeneous/src/lib.rs @@ -8,7 +8,7 @@ use core::{ }; use bare_metal::Nr; -use rtfm::Monotonic; +use rtfm::{Monotonic, MultiCore}; // both cores have the exact same interrupts pub use Interrupt_0 as Interrupt_1; @@ -21,7 +21,7 @@ pub fn xpend(_core: u8, _interrupt: impl Nr) {} /// Fake monotonic timer pub struct MT; -unsafe impl Monotonic for MT { +impl Monotonic for MT { type Instant = Instant; fn ratio() -> u32 { @@ -41,6 +41,8 @@ unsafe impl Monotonic for MT { } } +impl MultiCore for MT {} + #[derive(Clone, Copy, Eq, PartialEq)] pub struct Instant(i32); diff --git a/homogeneous/src/lib.rs b/homogeneous/src/lib.rs index a4f0ec5..3288bfe 100644 --- a/homogeneous/src/lib.rs +++ b/homogeneous/src/lib.rs @@ -8,7 +8,7 @@ use core::{ }; use bare_metal::Nr; -use rtfm::Monotonic; +use rtfm::{Monotonic, MultiCore}; // both cores have the exact same interrupts pub use Interrupt_0 as Interrupt_1; @@ -21,7 +21,7 @@ pub fn xpend(_core: u8, _interrupt: impl Nr) {} /// Fake monotonic timer pub struct MT; -unsafe impl Monotonic for MT { +impl Monotonic for MT { type Instant = Instant; fn ratio() -> u32 { @@ -41,6 +41,8 @@ unsafe impl Monotonic for MT { } } +impl MultiCore for MT {} + #[derive(Clone, Copy, Eq, PartialEq)] pub struct Instant(i32); diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs index 9276626..a351599 100644 --- a/macros/src/codegen.rs +++ b/macros/src/codegen.rs @@ -32,7 +32,7 @@ pub fn app(app: &App, analysis: &Analysis, extra: &Extra) -> TokenStream2 { // generate a `main` function for each core for core in 0..app.args.cores { - let assertion_stmts = assertions::codegen(core, analysis); + let assertion_stmts = assertions::codegen(core, analysis, extra); let (const_app_pre_init, pre_init_stmts) = pre_init::codegen(core, &app, analysis, extra); diff --git a/macros/src/codegen/assertions.rs b/macros/src/codegen/assertions.rs index 95268a2..4a77352 100644 --- a/macros/src/codegen/assertions.rs +++ b/macros/src/codegen/assertions.rs @@ -1,10 +1,10 @@ use proc_macro2::TokenStream as TokenStream2; use quote::quote; -use crate::analyze::Analysis; +use crate::{analyze::Analysis, check::Extra}; /// Generates compile-time assertions that check that types implement the `Send` / `Sync` traits -pub fn codegen(core: u8, analysis: &Analysis) -> Vec { +pub fn codegen(core: u8, analysis: &Analysis, extra: &Extra) -> Vec { let mut stmts = vec![]; // we don't generate *all* assertions on all cores because the user could conditionally import a @@ -22,5 +22,12 @@ pub fn codegen(core: u8, analysis: &Analysis) -> Vec { } } + // if the `schedule` API is used in more than one core then we need to check that the + // `monotonic` timer can be used in multi-core context + if analysis.timer_queues.len() > 1 && analysis.timer_queues.contains_key(&core) { + let monotonic = extra.monotonic(); + stmts.push(quote!(rtfm::export::assert_multicore::<#monotonic>();)); + } + stmts } diff --git a/src/cyccnt.rs b/src/cyccnt.rs index a2b216c..468aa71 100644 --- a/src/cyccnt.rs +++ b/src/cyccnt.rs @@ -116,6 +116,11 @@ pub struct Duration { } impl Duration { + /// Creates a new `Duration` from the specified number of clock cycles + pub fn from_cycles(cycles: u32) -> Self { + Duration { inner: cycles } + } + /// Returns the total number of clock cycles contained by this `Duration` pub fn as_cycles(&self) -> u32 { self.inner @@ -181,7 +186,7 @@ impl U32Ext for u32 { pub struct CYCCNT; #[cfg(not(feature = "heterogeneous"))] -unsafe impl crate::Monotonic for CYCCNT { +impl crate::Monotonic for CYCCNT { type Instant = Instant; fn ratio() -> u32 { diff --git a/src/export.rs b/src/export.rs index 7646e3c..572068c 100644 --- a/src/export.rs +++ b/src/export.rs @@ -108,6 +108,13 @@ where { } +#[inline(always)] +pub fn assert_multicore() +where + T: super::MultiCore, +{ +} + #[cfg(armv7m)] #[inline(always)] pub unsafe fn lock( diff --git a/src/lib.rs b/src/lib.rs index acb3a63..decd2da 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -117,7 +117,7 @@ impl From for Peripherals { } /// A monotonic clock / counter -pub unsafe trait Monotonic { +pub trait Monotonic { /// A measurement of this clock type Instant: Copy + Ord + Sub; @@ -134,6 +134,9 @@ pub unsafe trait Monotonic { fn zero() -> Self::Instant; } +/// A marker trait that indicates that it is correct to use this type in multi-core context +pub trait MultiCore {} + /// Sets the given `interrupt` as pending /// /// This is a convenience function around -- cgit v1.2.3 From df4a7fd3e5df370a83fcdc24aa628bed3fa9f543 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Mon, 24 Jun 2019 14:15:00 +0200 Subject: check that the app is not compiled for more cores than were specified --- macros/src/codegen.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'macros') diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs index a351599..8a54832 100644 --- a/macros/src/codegen.rs +++ b/macros/src/codegen.rs @@ -101,6 +101,18 @@ pub fn app(app: &App, analysis: &Analysis, extra: &Extra) -> TokenStream2 { let const_app_schedule = schedule::codegen(app, extra); + let cores = app.args.cores.to_string(); + let cfg_core = quote!(#[cfg(core = #cores)]); + let msg = format!( + "specified {} core{} but tried to compile for more than {0} core{1}", + app.args.cores, + if app.args.cores > 1 { "s" } else { "" } + ); + let check_excess_cores = quote!( + #cfg_core + compile_error!(#msg); + ); + let name = &app.name; let device = extra.device; quote!( @@ -124,6 +136,8 @@ pub fn app(app: &App, analysis: &Analysis, extra: &Extra) -> TokenStream2 { /// Always include the device crate which contains the vector table use #device as _; + #check_excess_cores + #(#const_app)* #(#const_app_resources)* -- cgit v1.2.3 From be92041a592f65f38cee8475b61d35e7fcee3694 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Sat, 29 Jun 2019 09:11:42 +0200 Subject: WIP --- build.rs | 5 ++++- macros/src/codegen.rs | 2 ++ macros/src/codegen/dispatchers.rs | 7 ++++++- macros/src/codegen/hardware_tasks.rs | 8 +++++++- macros/src/codegen/idle.rs | 13 +++++------- macros/src/codegen/init.rs | 4 +++- macros/src/codegen/locals.rs | 5 ++++- macros/src/codegen/resources.rs | 17 ++++++++++------ macros/src/codegen/schedule.rs | 4 ++++ macros/src/codegen/software_tasks.rs | 24 +++++++++++++++++++--- macros/src/codegen/spawn.rs | 4 ++++ macros/src/codegen/timer_queue.rs | 6 +++++- macros/src/codegen/util.rs | 39 ++++++++++++++++++++++++++++++++++++ 13 files changed, 115 insertions(+), 23 deletions(-) (limited to 'macros') diff --git a/build.rs b/build.rs index 2419b4e..14c3d24 100644 --- a/build.rs +++ b/build.rs @@ -7,7 +7,10 @@ fn main() { println!("cargo:rustc-cfg=armv6m") } - if target.starts_with("thumbv7m") | target.starts_with("thumbv7em") { + if target.starts_with("thumbv7m") + | target.starts_with("thumbv7em") + | target.starts_with("thumbv8m") + { println!("cargo:rustc-cfg=armv7m") } diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs index 8a54832..8ac06d5 100644 --- a/macros/src/codegen.rs +++ b/macros/src/codegen.rs @@ -68,8 +68,10 @@ pub fn app(app: &App, analysis: &Analysis, extra: &Extra) -> TokenStream2 { let cfg_core = util::cfg_core(core, app.args.cores); let main = util::suffixed("main", core); + let section = util::link_section("text", core); mains.push(quote!( #[no_mangle] + #section #cfg_core unsafe extern "C" fn #main() -> ! { #(#assertion_stmts)* diff --git a/macros/src/codegen/dispatchers.rs b/macros/src/codegen/dispatchers.rs index 988e3c8..9a9cb10 100644 --- a/macros/src/codegen/dispatchers.rs +++ b/macros/src/codegen/dispatchers.rs @@ -46,13 +46,14 @@ pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec), quote!(rtfm::export::Queue(unsafe { rtfm::export::iQueue::u8_sc() })), + util::link_section("bss", sender), ) } else { let shared = if cfg!(feature = "heterogeneous") { @@ -65,6 +66,7 @@ pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec), quote!(rtfm::export::Queue(rtfm::export::iQueue::u8())), + None, ) }; @@ -77,6 +79,7 @@ pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec Vec ! { use rtfm::Mutex as _; @@ -73,12 +75,7 @@ pub fn codegen( #name::Context::new(&rtfm::export::Priority::new(0)) )); - ( - const_app, - root_idle, - user_idle, - call_idle, - ) + (const_app, root_idle, user_idle, call_idle) } else { ( None, diff --git a/macros/src/codegen/init.rs b/macros/src/codegen/init.rs index 271be94..878c633 100644 --- a/macros/src/codegen/init.rs +++ b/macros/src/codegen/init.rs @@ -72,7 +72,7 @@ pub fn codegen( let mut locals_pat = None; let mut locals_new = None; if !init.locals.is_empty() { - let (struct_, pat) = locals::codegen(Context::Init(core), &init.locals, app); + let (struct_, pat) = locals::codegen(Context::Init(core), &init.locals, core, app); locals_new = Some(quote!(#name::Locals::new())); locals_pat = Some(pat); @@ -82,10 +82,12 @@ pub fn codegen( let context = &init.context; let attrs = &init.attrs; let stmts = &init.stmts; + let section = util::link_section("text", core); let user_init = Some(quote!( #(#attrs)* #cfg_core #[allow(non_snake_case)] + #section fn #name(#(#locals_pat,)* #context: #name::Context) #ret { #(#stmts)* } diff --git a/macros/src/codegen/locals.rs b/macros/src/codegen/locals.rs index 9663563..799ef7a 100644 --- a/macros/src/codegen/locals.rs +++ b/macros/src/codegen/locals.rs @@ -2,7 +2,7 @@ use proc_macro2::TokenStream as TokenStream2; use quote::quote; use rtfm_syntax::{ ast::{App, Local}, - Context, Map, + Context, Core, Map, }; use crate::codegen::util; @@ -10,6 +10,7 @@ use crate::codegen::util; pub fn codegen( ctxt: Context, locals: &Map, + core: Core, app: &App, ) -> ( // locals @@ -41,6 +42,7 @@ pub fn codegen( let cfgs = &local.cfgs; has_cfgs |= !cfgs.is_empty(); + let section = util::link_section("data", core); let expr = &local.expr; let ty = &local.ty; fields.push(quote!( @@ -49,6 +51,7 @@ pub fn codegen( )); items.push(quote!( #(#cfgs)* + #section static mut #name: #ty = #expr )); values.push(quote!( diff --git a/macros/src/codegen/resources.rs b/macros/src/codegen/resources.rs index 2425681..1161a7a 100644 --- a/macros/src/codegen/resources.rs +++ b/macros/src/codegen/resources.rs @@ -26,20 +26,24 @@ pub fn codegen( let ty = &res.ty; { - let loc_attr = match loc { + let (loc_attr, section) = match loc { Location::Owned { core, cross_initialized: false, - } => util::cfg_core(*core, app.args.cores), + } => ( + util::cfg_core(*core, app.args.cores), + util::link_section("data", *core), + ), // shared `static`s and cross-initialized resources need to be in `.shared` memory - _ => { + _ => ( if cfg!(feature = "heterogeneous") { Some(quote!(#[rtfm::export::shared])) } else { None - } - } + }, + None, + ), }; let (ty, expr) = if let Some(expr) = expr { @@ -53,9 +57,10 @@ pub fn codegen( let attrs = &res.attrs; const_app.push(quote!( - #loc_attr #(#attrs)* #(#cfgs)* + #loc_attr + #section static mut #name: #ty = #expr; )); } diff --git a/macros/src/codegen/schedule.rs b/macros/src/codegen/schedule.rs index 57f01a2..8cf6098 100644 --- a/macros/src/codegen/schedule.rs +++ b/macros/src/codegen/schedule.rs @@ -35,8 +35,10 @@ pub fn codegen(app: &App, extra: &Extra) -> Vec { let body = schedule_body::codegen(scheduler, &name, app); + let section = util::link_section("text", sender); methods.push(quote!( #(#cfgs)* + #section fn #name(&self, instant: #instant #(,#args)*) -> Result<(), #ty> { #body } @@ -50,9 +52,11 @@ pub fn codegen(app: &App, extra: &Extra) -> Vec { let body = schedule_body::codegen(scheduler, &name, app); + let section = util::link_section("text", sender); items.push(quote!( #cfg_sender #(#cfgs)* + #section unsafe fn #schedule( priority: &rtfm::export::Priority, instant: #instant diff --git a/macros/src/codegen/software_tasks.rs b/macros/src/codegen/software_tasks.rs index 383a5d8..2960faf 100644 --- a/macros/src/codegen/software_tasks.rs +++ b/macros/src/codegen/software_tasks.rs @@ -43,13 +43,21 @@ pub fn codegen( let cfg_sender = util::cfg_core(sender, app.args.cores); let fq = util::fq_ident(name, sender); - let (loc, fq_ty, fq_expr) = if receiver == sender { + let (loc, fq_ty, fq_expr, bss, mk_uninit): ( + _, + _, + _, + _, + Box Option<_>>, + ) = if receiver == sender { ( cfg_sender.clone(), quote!(rtfm::export::SCFQ<#cap_ty>), quote!(rtfm::export::Queue(unsafe { rtfm::export::iQueue::u8_sc() })), + util::link_section("bss", sender), + Box::new(|| util::link_section_uninit(Some(sender))), ) } else { let shared = if cfg!(feature = "heterogeneous") { @@ -62,6 +70,8 @@ pub fn codegen( shared, quote!(rtfm::export::MCFQ<#cap_ty>), quote!(rtfm::export::Queue(rtfm::export::iQueue::u8())), + None, + Box::new(|| util::link_section_uninit(None)), ) }; let loc = &loc; @@ -70,6 +80,7 @@ pub fn codegen( /// Queue version of a free-list that keeps track of empty slots in /// the following buffers #loc + #bss static mut #fq: #fq_ty = #fq_expr; )); @@ -102,8 +113,10 @@ pub fn codegen( let m = extra.monotonic(); let instants = util::instants_ident(name, sender); + let uninit = mk_uninit(); const_app.push(quote!( #loc + #uninit /// Buffer that holds the instants associated to the inputs of a task static mut #instants: [core::mem::MaybeUninit<<#m as rtfm::Monotonic>::Instant>; #cap_lit] = @@ -111,9 +124,11 @@ pub fn codegen( )); } + let uninit = mk_uninit(); let inputs = util::inputs_ident(name, sender); const_app.push(quote!( #loc + #uninit /// Buffer that holds the inputs of a task static mut #inputs: [core::mem::MaybeUninit<#input_ty>; #cap_lit] = [#(#elems,)*]; @@ -140,13 +155,15 @@ pub fn codegen( // `${task}Locals` let mut locals_pat = None; if !task.locals.is_empty() { - let (struct_, pat) = locals::codegen(Context::SoftwareTask(name), &task.locals, app); + let (struct_, pat) = + locals::codegen(Context::SoftwareTask(name), &task.locals, receiver, app); locals_pat = Some(pat); root.push(struct_); } let cfg_receiver = util::cfg_core(receiver, app.args.cores); + let section = util::link_section("text", receiver); let context = &task.context; let attrs = &task.attrs; let cfgs = &task.cfgs; @@ -154,8 +171,9 @@ pub fn codegen( user_tasks.push(quote!( #(#attrs)* #(#cfgs)* - #cfg_receiver #[allow(non_snake_case)] + #cfg_receiver + #section fn #name(#(#locals_pat,)* #context: #name::Context #(,#inputs)*) { use rtfm::Mutex as _; diff --git a/macros/src/codegen/spawn.rs b/macros/src/codegen/spawn.rs index 1539e27..c63c410 100644 --- a/macros/src/codegen/spawn.rs +++ b/macros/src/codegen/spawn.rs @@ -42,8 +42,10 @@ pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec Result<(), #ty> { #let_instant #body @@ -66,9 +68,11 @@ pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec Vec); + let section = util::link_section("bss", sender); items.push(quote!( #cfg_sender #[doc = #doc] + #section static mut #tq: #tq_ty = rtfm::export::TimerQueue( rtfm::export::BinaryHeap( rtfm::export::iBinaryHeap::new() @@ -117,9 +119,11 @@ pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec Ident { ) } +fn link_section_index() -> usize { + static INDEX: AtomicUsize = AtomicUsize::new(0); + + INDEX.fetch_add(1, Ordering::Relaxed) +} + +pub fn link_section(section: &str, core: Core) -> Option { + if cfg!(feature = "homogeneous") { + let section = format!(".{}_{}.rtfm{}", section, core, link_section_index()); + Some(quote!(#[link_section = #section])) + } else { + None + } +} + +// NOTE `None` means in shared memory +pub fn link_section_uninit(core: Option) -> Option { + let section = if let Some(core) = core { + let index = link_section_index(); + + if cfg!(feature = "homogeneous") { + format!(".uninit_{}.rtfm{}", core, index) + } else { + format!(".uninit.rtfm{}", index) + } + } else { + if cfg!(feature = "heterogeneous") { + // `#[shared]` attribute sets the linker section + return None; + } + + format!(".uninit.rtfm{}", link_section_index()) + }; + + Some(quote!(#[link_section = #section])) +} + /// Generates a pre-reexport identifier for the "locals" struct pub fn locals_ident(ctxt: Context, app: &App) -> Ident { let mut s = match ctxt { -- cgit v1.2.3 From 14d63f496118f4243f28ddf3218523aa36a80322 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Wed, 3 Jul 2019 20:36:52 +0200 Subject: fix (cross-core) initialization barriers --- macros/src/codegen/post_init.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) (limited to 'macros') diff --git a/macros/src/codegen/post_init.rs b/macros/src/codegen/post_init.rs index 3f1e445..19773e4 100644 --- a/macros/src/codegen/post_init.rs +++ b/macros/src/codegen/post_init.rs @@ -25,8 +25,12 @@ pub fn codegen( if analysis.timer_queues.is_empty() { // cross-initialization barriers -- notify *other* cores that their resources have been // initialized - if analysis.initialization_barriers.contains_key(&core) { - let ib = util::init_barrier(core); + for (user, initializers) in &analysis.initialization_barriers { + if !initializers.contains(&core) { + continue; + } + + let ib = util::init_barrier(*user); let shared = if cfg!(feature = "heterogeneous") { Some(quote!( #[rtfm::export::shared] @@ -46,14 +50,12 @@ pub fn codegen( } // then wait until the other cores have initialized *our* resources - for (&initializer, users) in &analysis.initialization_barriers { - if users.contains(&core) { - let ib = util::init_barrier(initializer); + if analysis.initialization_barriers.contains_key(&core) { + let ib = util::init_barrier(core); - stmts.push(quote!( - #ib.wait(); - )); - } + stmts.push(quote!( + #ib.wait(); + )); } // cross-spawn barriers: wait until other cores are ready to receive messages -- cgit v1.2.3 From 9195038c87703fc94b6e99f6de593886d51c2b19 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Wed, 10 Jul 2019 22:42:44 +0200 Subject: implement RFC #212 --- ci/expected/generics.run | 6 +-- ci/expected/lock.run | 4 +- ci/expected/resource.run | 4 +- ci/expected/static.run | 4 +- examples/cfg.rs | 15 +++--- examples/generics.rs | 19 ++++--- examples/late.rs | 16 +++--- examples/lock.rs | 19 ++++--- examples/not-send.rs | 13 +++-- examples/not-sync.rs | 13 +++-- examples/resource.rs | 27 +++++----- examples/shared-with-init.rs | 13 +++-- examples/static.rs | 14 ++--- examples/t-cfg.rs | 11 ++-- examples/t-late-not-send.rs | 16 +++--- examples/t-resource.rs | 70 ++++++++++++++----------- examples/types.rs | 13 +++-- heterogeneous/examples/x-init-2.rs | 16 +++--- heterogeneous/examples/x-init.rs | 12 ++--- homogeneous/examples/x-init-2.rs | 16 +++--- homogeneous/examples/x-init.rs | 12 ++--- macros/src/codegen/resources.rs | 79 ++++++++++++++-------------- macros/src/codegen/resources_struct.rs | 8 ++- ui/single/resources-cfg.rs | 96 ++++++++++++++++++++-------------- ui/single/resources-cfg.stderr | 90 +++++++++++++++---------------- 25 files changed, 332 insertions(+), 274 deletions(-) (limited to 'macros') diff --git a/ci/expected/generics.run b/ci/expected/generics.run index 7fa9775..fb31731 100644 --- a/ci/expected/generics.run +++ b/ci/expected/generics.run @@ -1,6 +1,6 @@ UART1(STATE = 0) -SHARED: 0 -> 1 +shared: 0 -> 1 UART0(STATE = 0) -SHARED: 1 -> 2 +shared: 1 -> 2 UART1(STATE = 1) -SHARED: 2 -> 4 +shared: 2 -> 4 diff --git a/ci/expected/lock.run b/ci/expected/lock.run index 156ac22..a987b37 100644 --- a/ci/expected/lock.run +++ b/ci/expected/lock.run @@ -1,5 +1,5 @@ A -B - SHARED = 1 +B - shared = 1 C -D - SHARED = 2 +D - shared = 2 E diff --git a/ci/expected/resource.run b/ci/expected/resource.run index 9c70856..a587a94 100644 --- a/ci/expected/resource.run +++ b/ci/expected/resource.run @@ -1,2 +1,2 @@ -UART0: SHARED = 1 -UART1: SHARED = 2 +UART0: shared = 1 +UART1: shared = 2 diff --git a/ci/expected/static.run b/ci/expected/static.run index 2c295c9..1d4eed0 100644 --- a/ci/expected/static.run +++ b/ci/expected/static.run @@ -1,2 +1,2 @@ -UART1(KEY = 0xdeadbeef) -UART0(KEY = 0xdeadbeef) +UART1(key = 0xdeadbeef) +UART0(key = 0xdeadbeef) diff --git a/examples/cfg.rs b/examples/cfg.rs index b1f65cf..fb812cb 100644 --- a/examples/cfg.rs +++ b/examples/cfg.rs @@ -11,25 +11,28 @@ use panic_semihosting as _; #[rtfm::app(device = lm3s6965)] const APP: () = { - #[cfg(debug_assertions)] // <- `true` when using the `dev` profile - static mut COUNT: u32 = 0; + struct Resources { + #[cfg(debug_assertions)] // <- `true` when using the `dev` profile + #[init(0)] + count: u32, + } #[init] fn init(_: init::Context) { // .. } - #[task(priority = 3, resources = [COUNT], spawn = [log])] + #[task(priority = 3, resources = [count], spawn = [log])] fn foo(_c: foo::Context) { #[cfg(debug_assertions)] { - *_c.resources.COUNT += 1; + *_c.resources.count += 1; - _c.spawn.log(*_c.resources.COUNT).ok(); + _c.spawn.log(*_c.resources.count).ok(); } // this wouldn't compile in `release` mode - // *resources.COUNT += 1; + // *resources.count += 1; // .. } diff --git a/examples/generics.rs b/examples/generics.rs index 562470d..f0632d9 100644 --- a/examples/generics.rs +++ b/examples/generics.rs @@ -12,7 +12,10 @@ use rtfm::{Exclusive, Mutex}; #[rtfm::app(device = lm3s6965)] const APP: () = { - static mut SHARED: u32 = 0; + struct Resources { + #[init(0)] + shared: u32, + } #[init] fn init(_: init::Context) { @@ -20,29 +23,29 @@ const APP: () = { rtfm::pend(Interrupt::UART1); } - #[task(binds = UART0, resources = [SHARED])] + #[task(binds = UART0, resources = [shared])] fn uart0(c: uart0::Context) { static mut STATE: u32 = 0; hprintln!("UART0(STATE = {})", *STATE).unwrap(); - advance(STATE, c.resources.SHARED); + advance(STATE, c.resources.shared); rtfm::pend(Interrupt::UART1); debug::exit(debug::EXIT_SUCCESS); } - #[task(binds = UART1, priority = 2, resources = [SHARED])] + #[task(binds = UART1, priority = 2, resources = [shared])] fn uart1(c: uart1::Context) { static mut STATE: u32 = 0; hprintln!("UART1(STATE = {})", *STATE).unwrap(); - // just to show that `SHARED` can be accessed directly - *c.resources.SHARED += 0; + // just to show that `shared` can be accessed directly + *c.resources.shared += 0; - advance(STATE, Exclusive(c.resources.SHARED)); + advance(STATE, Exclusive(c.resources.shared)); } }; @@ -55,5 +58,5 @@ fn advance(state: &mut u32, mut shared: impl Mutex) { (old, *shared) }); - hprintln!("SHARED: {} -> {}", old, new).unwrap(); + hprintln!("shared: {} -> {}", old, new).unwrap(); } diff --git a/examples/late.rs b/examples/late.rs index 19807ff..536d71a 100644 --- a/examples/late.rs +++ b/examples/late.rs @@ -16,9 +16,9 @@ use panic_semihosting as _; #[rtfm::app(device = lm3s6965)] const APP: () = { // Late resources - extern "C" { - static mut P: Producer<'static, u32, U4>; - static mut C: Consumer<'static, u32, U4>; + struct Resources { + p: Producer<'static, u32, U4>, + c: Consumer<'static, u32, U4>, } #[init] @@ -31,13 +31,13 @@ const APP: () = { let (p, c) = Q.as_mut().unwrap().split(); // Initialization of late resources - init::LateResources { P: p, C: c } + init::LateResources { p, c } } - #[idle(resources = [C])] + #[idle(resources = [c])] fn idle(c: idle::Context) -> ! { loop { - if let Some(byte) = c.resources.C.dequeue() { + if let Some(byte) = c.resources.c.dequeue() { hprintln!("received message: {}", byte).unwrap(); debug::exit(debug::EXIT_SUCCESS); @@ -47,8 +47,8 @@ const APP: () = { } } - #[task(binds = UART0, resources = [P])] + #[task(binds = UART0, resources = [p])] fn uart0(c: uart0::Context) { - c.resources.P.enqueue(42).unwrap(); + c.resources.p.enqueue(42).unwrap(); } }; diff --git a/examples/lock.rs b/examples/lock.rs index 17b6a58..f33a60a 100644 --- a/examples/lock.rs +++ b/examples/lock.rs @@ -11,7 +11,10 @@ use panic_semihosting as _; #[rtfm::app(device = lm3s6965)] const APP: () = { - static mut SHARED: u32 = 0; + struct Resources { + #[init(0)] + shared: u32, + } #[init] fn init(_: init::Context) { @@ -19,21 +22,21 @@ const APP: () = { } // when omitted priority is assumed to be `1` - #[task(binds = GPIOA, resources = [SHARED])] + #[task(binds = GPIOA, resources = [shared])] fn gpioa(mut c: gpioa::Context) { hprintln!("A").unwrap(); // the lower priority task requires a critical section to access the data - c.resources.SHARED.lock(|shared| { + c.resources.shared.lock(|shared| { // data can only be modified within this critical section (closure) *shared += 1; // GPIOB will *not* run right now due to the critical section rtfm::pend(Interrupt::GPIOB); - hprintln!("B - SHARED = {}", *shared).unwrap(); + hprintln!("B - shared = {}", *shared).unwrap(); - // GPIOC does not contend for `SHARED` so it's allowed to run now + // GPIOC does not contend for `shared` so it's allowed to run now rtfm::pend(Interrupt::GPIOC); }); @@ -44,12 +47,12 @@ const APP: () = { debug::exit(debug::EXIT_SUCCESS); } - #[task(binds = GPIOB, priority = 2, resources = [SHARED])] + #[task(binds = GPIOB, priority = 2, resources = [shared])] fn gpiob(c: gpiob::Context) { // the higher priority task does *not* need a critical section - *c.resources.SHARED += 1; + *c.resources.shared += 1; - hprintln!("D - SHARED = {}", *c.resources.SHARED).unwrap(); + hprintln!("D - shared = {}", *c.resources.shared).unwrap(); } #[task(binds = GPIOC, priority = 3)] diff --git a/examples/not-send.rs b/examples/not-send.rs index f240e51..d27cc82 100644 --- a/examples/not-send.rs +++ b/examples/not-send.rs @@ -17,7 +17,10 @@ pub struct NotSend { #[app(device = lm3s6965)] const APP: () = { - static mut SHARED: Option = None; + struct Resources { + #[init(None)] + shared: Option, + } #[init(spawn = [baz, quux])] fn init(c: init::Context) { @@ -36,16 +39,16 @@ const APP: () = { // scenario 1 } - #[task(priority = 2, resources = [SHARED])] + #[task(priority = 2, resources = [shared])] fn baz(c: baz::Context) { // scenario 2: resource shared between tasks that run at the same priority - *c.resources.SHARED = Some(NotSend { _0: PhantomData }); + *c.resources.shared = Some(NotSend { _0: PhantomData }); } - #[task(priority = 2, resources = [SHARED])] + #[task(priority = 2, resources = [shared])] fn quux(c: quux::Context) { // scenario 2 - let _not_send = c.resources.SHARED.take().unwrap(); + let _not_send = c.resources.shared.take().unwrap(); debug::exit(debug::EXIT_SUCCESS); } diff --git a/examples/not-sync.rs b/examples/not-sync.rs index 6b49911..f0f6075 100644 --- a/examples/not-sync.rs +++ b/examples/not-sync.rs @@ -16,21 +16,24 @@ pub struct NotSync { #[rtfm::app(device = lm3s6965)] const APP: () = { - static SHARED: NotSync = NotSync { _0: PhantomData }; + struct Resources { + #[init(NotSync { _0: PhantomData })] + shared: NotSync, + } #[init] fn init(_: init::Context) { debug::exit(debug::EXIT_SUCCESS); } - #[task(resources = [SHARED])] + #[task(resources = [shared])] fn foo(c: foo::Context) { - let _: &NotSync = c.resources.SHARED; + let _: &NotSync = c.resources.shared; } - #[task(resources = [SHARED])] + #[task(resources = [shared])] fn bar(c: bar::Context) { - let _: &NotSync = c.resources.SHARED; + let _: &NotSync = c.resources.shared; } extern "C" { diff --git a/examples/resource.rs b/examples/resource.rs index 661f8c3..2506a2c 100644 --- a/examples/resource.rs +++ b/examples/resource.rs @@ -11,8 +11,11 @@ use panic_semihosting as _; #[rtfm::app(device = lm3s6965)] const APP: () = { - // A resource - static mut SHARED: u32 = 0; + struct Resources { + // A resource + #[init(0)] + shared: u32, + } #[init] fn init(_: init::Context) { @@ -24,25 +27,25 @@ const APP: () = { fn idle(_: idle::Context) -> ! { debug::exit(debug::EXIT_SUCCESS); - // error: `SHARED` can't be accessed from this context - // SHARED += 1; + // error: `shared` can't be accessed from this context + // shared += 1; loop {} } - // `SHARED` can be access from this context - #[task(binds = UART0, resources = [SHARED])] + // `shared` can be access from this context + #[task(binds = UART0, resources = [shared])] fn uart0(c: uart0::Context) { - *c.resources.SHARED += 1; + *c.resources.shared += 1; - hprintln!("UART0: SHARED = {}", c.resources.SHARED).unwrap(); + hprintln!("UART0: shared = {}", c.resources.shared).unwrap(); } - // `SHARED` can be access from this context - #[task(binds = UART1, resources = [SHARED])] + // `shared` can be access from this context + #[task(binds = UART1, resources = [shared])] fn uart1(c: uart1::Context) { - *c.resources.SHARED += 1; + *c.resources.shared += 1; - hprintln!("UART1: SHARED = {}", c.resources.SHARED).unwrap(); + hprintln!("UART1: shared = {}", c.resources.shared).unwrap(); } }; diff --git a/examples/shared-with-init.rs b/examples/shared-with-init.rs index ed73c8b..14fa54b 100644 --- a/examples/shared-with-init.rs +++ b/examples/shared-with-init.rs @@ -14,20 +14,23 @@ pub struct MustBeSend; #[app(device = lm3s6965)] const APP: () = { - static mut SHARED: Option = None; + struct Resources { + #[init(None)] + shared: Option, + } - #[init(resources = [SHARED])] + #[init(resources = [shared])] fn init(c: init::Context) { // this `message` will be sent to task `UART0` let message = MustBeSend; - *c.resources.SHARED = Some(message); + *c.resources.shared = Some(message); rtfm::pend(Interrupt::UART0); } - #[task(binds = UART0, resources = [SHARED])] + #[task(binds = UART0, resources = [shared])] fn uart0(c: uart0::Context) { - if let Some(message) = c.resources.SHARED.take() { + if let Some(message) = c.resources.shared.take() { // `message` has been received drop(message); diff --git a/examples/static.rs b/examples/static.rs index 5eb7b19..4af7ee6 100644 --- a/examples/static.rs +++ b/examples/static.rs @@ -11,8 +11,8 @@ use panic_semihosting as _; #[rtfm::app(device = lm3s6965)] const APP: () = { - extern "C" { - static KEY: u32; + struct Resources { + key: u32, } #[init] @@ -20,18 +20,18 @@ const APP: () = { rtfm::pend(Interrupt::UART0); rtfm::pend(Interrupt::UART1); - init::LateResources { KEY: 0xdeadbeef } + init::LateResources { key: 0xdeadbeef } } - #[task(binds = UART0, resources = [KEY])] + #[task(binds = UART0, resources = [&key])] fn uart0(c: uart0::Context) { - hprintln!("UART0(KEY = {:#x})", c.resources.KEY).unwrap(); + hprintln!("UART0(key = {:#x})", c.resources.key).unwrap(); debug::exit(debug::EXIT_SUCCESS); } - #[task(binds = UART1, priority = 2, resources = [KEY])] + #[task(binds = UART1, priority = 2, resources = [&key])] fn uart1(c: uart1::Context) { - hprintln!("UART1(KEY = {:#x})", c.resources.KEY).unwrap(); + hprintln!("UART1(key = {:#x})", c.resources.key).unwrap(); } }; diff --git a/examples/t-cfg.rs b/examples/t-cfg.rs index 158eef5..e61ec79 100644 --- a/examples/t-cfg.rs +++ b/examples/t-cfg.rs @@ -7,8 +7,11 @@ use panic_halt as _; #[rtfm::app(device = lm3s6965, monotonic = rtfm::cyccnt::CYCCNT)] const APP: () = { - #[cfg(never)] - static mut FOO: u32 = 0; + struct Resources { + #[cfg(never)] + #[init(0)] + foo: u32, + } #[init] fn init(_: init::Context) { @@ -24,13 +27,13 @@ const APP: () = { loop {} } - #[task(resources = [FOO], schedule = [quux], spawn = [quux])] + #[task(resources = [foo], schedule = [quux], spawn = [quux])] fn foo(_: foo::Context) { #[cfg(never)] static mut BAR: u32 = 0; } - #[task(priority = 3, resources = [FOO], schedule = [quux], spawn = [quux])] + #[task(priority = 3, resources = [foo], schedule = [quux], spawn = [quux])] fn bar(_: bar::Context) { #[cfg(never)] static mut BAR: u32 = 0; diff --git a/examples/t-late-not-send.rs b/examples/t-late-not-send.rs index 55a053d..4fd3504 100644 --- a/examples/t-late-not-send.rs +++ b/examples/t-late-not-send.rs @@ -13,23 +13,23 @@ pub struct NotSend { #[rtfm::app(device = lm3s6965)] const APP: () = { - extern "C" { - static mut X: NotSend; + struct Resources { + x: NotSend, + #[init(None)] + y: Option, } - static mut Y: Option = None; - - #[init(resources = [Y])] + #[init(resources = [y])] fn init(c: init::Context) -> init::LateResources { // equivalent to late resource initialization - *c.resources.Y = Some(NotSend { _0: PhantomData }); + *c.resources.y = Some(NotSend { _0: PhantomData }); init::LateResources { - X: NotSend { _0: PhantomData }, + x: NotSend { _0: PhantomData }, } } - #[idle(resources = [X, Y])] + #[idle(resources = [x, y])] fn idle(_: idle::Context) -> ! { loop {} } diff --git a/examples/t-resource.rs b/examples/t-resource.rs index adcc04b..303340e 100644 --- a/examples/t-resource.rs +++ b/examples/t-resource.rs @@ -9,69 +9,79 @@ use panic_halt as _; #[rtfm::app(device = lm3s6965)] const APP: () = { - static mut O1: u32 = 0; // init - static mut O2: u32 = 0; // idle - static mut O3: u32 = 0; // EXTI0 - static O4: u32 = 0; // idle - static O5: u32 = 0; // EXTI1 - static O6: u32 = 0; // init - - static mut S1: u32 = 0; // idle & EXTI0 - static mut S2: u32 = 0; // EXTI0 & EXTI1 - static S3: u32 = 0; - - #[init(resources = [O1, O4, O5, O6, S3])] + struct Resources { + #[init(0)] + o1: u32, // init + #[init(0)] + o2: u32, // idle + #[init(0)] + o3: u32, // EXTI0 + #[init(0)] + o4: u32, // idle + #[init(0)] + o5: u32, // EXTI1 + #[init(0)] + o6: u32, // init + #[init(0)] + s1: u32, // idle & uart0 + #[init(0)] + s2: u32, // uart0 & uart1 + #[init(0)] + s3: u32, // idle & uart0 + } + + #[init(resources = [o1, o4, o5, o6, s3])] fn init(c: init::Context) { // owned by `init` == `&'static mut` - let _: &'static mut u32 = c.resources.O1; + let _: &'static mut u32 = c.resources.o1; // owned by `init` == `&'static` if read-only - let _: &'static u32 = c.resources.O6; + let _: &'static u32 = c.resources.o6; // `init` has exclusive access to all resources - let _: &mut u32 = c.resources.O4; - let _: &mut u32 = c.resources.O5; - let _: &mut u32 = c.resources.S3; + let _: &mut u32 = c.resources.o4; + let _: &mut u32 = c.resources.o5; + let _: &mut u32 = c.resources.s3; } - #[idle(resources = [O2, O4, S1, S3])] + #[idle(resources = [o2, &o4, s1, &s3])] fn idle(mut c: idle::Context) -> ! { // owned by `idle` == `&'static mut` - let _: &'static mut u32 = c.resources.O2; + let _: &'static mut u32 = c.resources.o2; // owned by `idle` == `&'static` if read-only - let _: &'static u32 = c.resources.O4; + let _: &'static u32 = c.resources.o4; // shared with `idle` == `Mutex` - c.resources.S1.lock(|_| {}); + c.resources.s1.lock(|_| {}); // `&` if read-only - let _: &u32 = c.resources.S3; + let _: &u32 = c.resources.s3; loop {} } - #[task(binds = UART0, resources = [O3, S1, S2, S3])] + #[task(binds = UART0, resources = [o3, s1, s2, &s3])] fn uart0(c: uart0::Context) { // owned by interrupt == `&mut` - let _: &mut u32 = c.resources.O3; + let _: &mut u32 = c.resources.o3; // no `Mutex` proxy when access from highest priority task - let _: &mut u32 = c.resources.S1; + let _: &mut u32 = c.resources.s1; // no `Mutex` proxy when co-owned by cooperative (same priority) tasks - let _: &mut u32 = c.resources.S2; + let _: &mut u32 = c.resources.s2; // `&` if read-only - let _: &u32 = c.resources.S3; + let _: &u32 = c.resources.s3; } - #[task(binds = UART1, resources = [S2, O5])] + #[task(binds = UART1, resources = [s2, &o5])] fn uart1(c: uart1::Context) { // owned by interrupt == `&` if read-only - let _: &u32 = c.resources.O5; + let _: &u32 = c.resources.o5; // no `Mutex` proxy when co-owned by cooperative (same priority) tasks - let _: &mut u32 = c.resources.S2; + let _: &mut u32 = c.resources.s2; } }; diff --git a/examples/types.rs b/examples/types.rs index 3e9c7ea..0c8097f 100644 --- a/examples/types.rs +++ b/examples/types.rs @@ -11,7 +11,10 @@ use rtfm::cyccnt::Instant; #[rtfm::app(device = lm3s6965, peripherals = true, monotonic = rtfm::cyccnt::CYCCNT)] const APP: () = { - static mut SHARED: u32 = 0; + struct Resources { + #[init(0)] + shared: u32, + } #[init(schedule = [foo], spawn = [foo])] fn init(c: init::Context) { @@ -31,18 +34,18 @@ const APP: () = { let _: svcall::Spawn = c.spawn; } - #[task(binds = UART0, resources = [SHARED], schedule = [foo], spawn = [foo])] + #[task(binds = UART0, resources = [shared], schedule = [foo], spawn = [foo])] fn uart0(c: uart0::Context) { let _: Instant = c.start; - let _: resources::SHARED = c.resources.SHARED; + let _: resources::shared = c.resources.shared; let _: uart0::Schedule = c.schedule; let _: uart0::Spawn = c.spawn; } - #[task(priority = 2, resources = [SHARED], schedule = [foo], spawn = [foo])] + #[task(priority = 2, resources = [shared], schedule = [foo], spawn = [foo])] fn foo(c: foo::Context) { let _: Instant = c.scheduled; - let _: &mut u32 = c.resources.SHARED; + let _: &mut u32 = c.resources.shared; let _: foo::Resources = c.resources; let _: foo::Schedule = c.schedule; let _: foo::Spawn = c.spawn; diff --git a/heterogeneous/examples/x-init-2.rs b/heterogeneous/examples/x-init-2.rs index b9c3919..033753c 100644 --- a/heterogeneous/examples/x-init-2.rs +++ b/heterogeneous/examples/x-init-2.rs @@ -9,30 +9,30 @@ use panic_halt as _; #[rtfm::app(cores = 2, device = heterogeneous)] const APP: () = { - extern "C" { + struct Resources { // owned by core #1 but initialized by core #0 - static mut X: u32; + x: u32, // owned by core #0 but initialized by core #1 - static mut Y: u32; + y: u32, } - #[init(core = 0, late = [X])] + #[init(core = 0, late = [x])] fn a(_: a::Context) -> a::LateResources { - a::LateResources { X: 0 } + a::LateResources { x: 0 } } - #[idle(core = 0, resources = [Y])] + #[idle(core = 0, resources = [y])] fn b(_: b::Context) -> ! { loop {} } #[init(core = 1)] fn c(_: c::Context) -> c::LateResources { - c::LateResources { Y: 0 } + c::LateResources { y: 0 } } - #[idle(core = 1, resources = [X])] + #[idle(core = 1, resources = [x])] fn d(_: d::Context) -> ! { loop {} } diff --git a/heterogeneous/examples/x-init.rs b/heterogeneous/examples/x-init.rs index 53e7380..4183713 100644 --- a/heterogeneous/examples/x-init.rs +++ b/heterogeneous/examples/x-init.rs @@ -9,18 +9,18 @@ use panic_halt as _; #[rtfm::app(cores = 2, device = heterogeneous)] const APP: () = { - extern "C" { - static mut X: u32; - static mut Y: u32; + struct Resources { + x: u32, + y: u32, } - #[init(core = 0, late = [X])] + #[init(core = 0, late = [x])] fn a(_: a::Context) -> a::LateResources { - a::LateResources { X: 0 } + a::LateResources { x: 0 } } #[init(core = 1)] fn b(_: b::Context) -> b::LateResources { - b::LateResources { Y: 0 } + b::LateResources { y: 0 } } }; diff --git a/homogeneous/examples/x-init-2.rs b/homogeneous/examples/x-init-2.rs index f51e2f6..de35cf6 100644 --- a/homogeneous/examples/x-init-2.rs +++ b/homogeneous/examples/x-init-2.rs @@ -9,30 +9,30 @@ use panic_halt as _; #[rtfm::app(cores = 2, device = homogeneous)] const APP: () = { - extern "C" { + struct Resources { // owned by core #1 but initialized by core #0 - static mut X: u32; + x: u32, // owned by core #0 but initialized by core #1 - static mut Y: u32; + y: u32, } - #[init(core = 0, late = [X])] + #[init(core = 0, late = [x])] fn a(_: a::Context) -> a::LateResources { - a::LateResources { X: 0 } + a::LateResources { x: 0 } } - #[idle(core = 0, resources = [Y])] + #[idle(core = 0, resources = [y])] fn b(_: b::Context) -> ! { loop {} } #[init(core = 1)] fn c(_: c::Context) -> c::LateResources { - c::LateResources { Y: 0 } + c::LateResources { y: 0 } } - #[idle(core = 1, resources = [X])] + #[idle(core = 1, resources = [x])] fn d(_: d::Context) -> ! { loop {} } diff --git a/homogeneous/examples/x-init.rs b/homogeneous/examples/x-init.rs index 5089e38..c359901 100644 --- a/homogeneous/examples/x-init.rs +++ b/homogeneous/examples/x-init.rs @@ -9,18 +9,18 @@ use panic_halt as _; #[rtfm::app(cores = 2, device = homogeneous)] const APP: () = { - extern "C" { - static mut X: u32; - static mut Y: u32; + struct Resources { + x: u32, + y: u32, } - #[init(core = 0, late = [X])] + #[init(core = 0, late = [x])] fn a(_: a::Context) -> a::LateResources { - a::LateResources { X: 0 } + a::LateResources { x: 0 } } #[init(core = 1)] fn b(_: b::Context) -> b::LateResources { - b::LateResources { Y: 0 } + b::LateResources { y: 0 } } }; diff --git a/macros/src/codegen/resources.rs b/macros/src/codegen/resources.rs index 1161a7a..bec4602 100644 --- a/macros/src/codegen/resources.rs +++ b/macros/src/codegen/resources.rs @@ -57,6 +57,7 @@ pub fn codegen( let attrs = &res.attrs; const_app.push(quote!( + #[allow(non_upper_case_globals)] #(#attrs)* #(#cfgs)* #loc_attr @@ -65,50 +66,48 @@ pub fn codegen( )); } - // generate a resource proxy if needed - if res.mutability.is_some() { - if let Some(Ownership::Shared { ceiling }) = analysis.ownerships.get(name) { - let cfg_core = util::cfg_core(loc.core().expect("UNREACHABLE"), app.args.cores); + if let Some(Ownership::Contended { ceiling }) = analysis.ownerships.get(name) { + let cfg_core = util::cfg_core(loc.core().expect("UNREACHABLE"), app.args.cores); - mod_resources.push(quote!( - #(#cfgs)* - #cfg_core - pub struct #name<'a> { - priority: &'a Priority, + mod_resources.push(quote!( + #[allow(non_camel_case_types)] + #(#cfgs)* + #cfg_core + pub struct #name<'a> { + priority: &'a Priority, + } + + #(#cfgs)* + #cfg_core + impl<'a> #name<'a> { + #[inline(always)] + pub unsafe fn new(priority: &'a Priority) -> Self { + #name { priority } } - #(#cfgs)* - #cfg_core - impl<'a> #name<'a> { - #[inline(always)] - pub unsafe fn new(priority: &'a Priority) -> Self { - #name { priority } - } - - #[inline(always)] - pub unsafe fn priority(&self) -> &Priority { - self.priority - } + #[inline(always)] + pub unsafe fn priority(&self) -> &Priority { + self.priority } - )); - - let ptr = if expr.is_none() { - quote!(#name.as_mut_ptr()) - } else { - quote!(&mut #name) - }; - - const_app.push(util::impl_mutex( - extra, - cfgs, - cfg_core.as_ref(), - true, - name, - quote!(#ty), - *ceiling, - ptr, - )); - } + } + )); + + let ptr = if expr.is_none() { + quote!(#name.as_mut_ptr()) + } else { + quote!(&mut #name) + }; + + const_app.push(util::impl_mutex( + extra, + cfgs, + cfg_core.as_ref(), + true, + name, + quote!(#ty), + *ceiling, + ptr, + )); } } diff --git a/macros/src/codegen/resources_struct.rs b/macros/src/codegen/resources_struct.rs index 0248f19..07a6061 100644 --- a/macros/src/codegen/resources_struct.rs +++ b/macros/src/codegen/resources_struct.rs @@ -24,13 +24,17 @@ pub fn codegen( let mut values = vec![]; let mut has_cfgs = false; - for name in resources { + for (name, access) in resources { let (res, expr) = app.resource(name).expect("UNREACHABLE"); let cfgs = &res.cfgs; has_cfgs |= !cfgs.is_empty(); - let mut_ = res.mutability; + let mut_ = if access.is_exclusive() { + Some(quote!(mut)) + } else { + None + }; let ty = &res.ty; if ctxt.is_init() { diff --git a/ui/single/resources-cfg.rs b/ui/single/resources-cfg.rs index 6f608fa..906b3e2 100644 --- a/ui/single/resources-cfg.rs +++ b/ui/single/resources-cfg.rs @@ -2,56 +2,74 @@ #[rtfm::app(device = lm3s6965)] const APP: () = { - #[cfg(never)] - static mut O1: u32 = 0; // init - #[cfg(never)] - static mut O2: u32 = 0; // idle - #[cfg(never)] - static mut O3: u32 = 0; // EXTI0 - #[cfg(never)] - static O4: u32 = 0; // idle - #[cfg(never)] - static O5: u32 = 0; // EXTI1 - #[cfg(never)] - static O6: u32 = 0; // init - - #[cfg(never)] - static mut S1: u32 = 0; // idle & EXTI0 - #[cfg(never)] - static mut S2: u32 = 0; // EXTI0 & EXTI1 - #[cfg(never)] - static S3: u32 = 0; - - #[init(resources = [O1, O4, O5, O6, S3])] + struct Resources { + #[cfg(never)] + #[init(0)] + o1: u32, // init + + #[cfg(never)] + #[init(0)] + o2: u32, // idle + + #[cfg(never)] + #[init(0)] + o3: u32, // EXTI0 + + #[cfg(never)] + #[init(0)] + o4: u32, // idle + + #[cfg(never)] + #[init(0)] + o5: u32, // EXTI1 + + #[cfg(never)] + #[init(0)] + o6: u32, // init + + #[cfg(never)] + #[init(0)] + s1: u32, // idle & EXTI0 + + #[cfg(never)] + #[init(0)] + s2: u32, // EXTI0 & EXTI1 + + #[cfg(never)] + #[init(0)] + s3: u32, + } + + #[init(resources = [o1, o4, o5, o6, s3])] fn init(c: init::Context) { - c.resources.O1; - c.resources.O4; - c.resources.O5; - c.resources.O6; - c.resources.S3; + c.resources.o1; + c.resources.o4; + c.resources.o5; + c.resources.o6; + c.resources.s3; } - #[idle(resources = [O2, O4, S1, S3])] + #[idle(resources = [o2, &o4, s1, &s3])] fn idle(c: idle::Context) -> ! { - c.resources.O2; - c.resources.O4; - c.resources.S1; - c.resources.S3; + c.resources.o2; + c.resources.o4; + c.resources.s1; + c.resources.s3; loop {} } - #[task(binds = UART0, resources = [O3, S1, S2, S3])] + #[task(binds = UART0, resources = [o3, s1, s2, &s3])] fn uart0(c: uart0::Context) { - c.resources.O3; - c.resources.S1; - c.resources.S2; - c.resources.S3; + c.resources.o3; + c.resources.s1; + c.resources.s2; + c.resources.s3; } - #[task(binds = UART1, resources = [S2, O5])] + #[task(binds = UART1, resources = [s2, &o5])] fn uart1(c: uart1::Context) { - c.resources.S2; - c.resources.O5; + c.resources.s2; + c.resources.o5; } }; diff --git a/ui/single/resources-cfg.stderr b/ui/single/resources-cfg.stderr index 55e7ee0..a745e6e 100644 --- a/ui/single/resources-cfg.stderr +++ b/ui/single/resources-cfg.stderr @@ -1,119 +1,119 @@ -error[E0609]: no field `O1` on type `initResources<'_>` - --> $DIR/resources-cfg.rs:27:21 +error[E0609]: no field `o1` on type `initResources<'_>` + --> $DIR/resources-cfg.rs:45:21 | -27 | c.resources.O1; +45 | c.resources.o1; | ^^ unknown field | = note: available fields are: `__marker__` -error[E0609]: no field `O4` on type `initResources<'_>` - --> $DIR/resources-cfg.rs:28:21 +error[E0609]: no field `o4` on type `initResources<'_>` + --> $DIR/resources-cfg.rs:46:21 | -28 | c.resources.O4; +46 | c.resources.o4; | ^^ unknown field | = note: available fields are: `__marker__` -error[E0609]: no field `O5` on type `initResources<'_>` - --> $DIR/resources-cfg.rs:29:21 +error[E0609]: no field `o5` on type `initResources<'_>` + --> $DIR/resources-cfg.rs:47:21 | -29 | c.resources.O5; +47 | c.resources.o5; | ^^ unknown field | = note: available fields are: `__marker__` -error[E0609]: no field `O6` on type `initResources<'_>` - --> $DIR/resources-cfg.rs:30:21 +error[E0609]: no field `o6` on type `initResources<'_>` + --> $DIR/resources-cfg.rs:48:21 | -30 | c.resources.O6; +48 | c.resources.o6; | ^^ unknown field | = note: available fields are: `__marker__` -error[E0609]: no field `S3` on type `initResources<'_>` - --> $DIR/resources-cfg.rs:31:21 +error[E0609]: no field `s3` on type `initResources<'_>` + --> $DIR/resources-cfg.rs:49:21 | -31 | c.resources.S3; +49 | c.resources.s3; | ^^ unknown field | = note: available fields are: `__marker__` -error[E0609]: no field `O2` on type `idleResources<'_>` - --> $DIR/resources-cfg.rs:36:21 +error[E0609]: no field `o2` on type `idleResources<'_>` + --> $DIR/resources-cfg.rs:54:21 | -36 | c.resources.O2; +54 | c.resources.o2; | ^^ unknown field | = note: available fields are: `__marker__` -error[E0609]: no field `O4` on type `idleResources<'_>` - --> $DIR/resources-cfg.rs:37:21 +error[E0609]: no field `o4` on type `idleResources<'_>` + --> $DIR/resources-cfg.rs:55:21 | -37 | c.resources.O4; +55 | c.resources.o4; | ^^ unknown field | = note: available fields are: `__marker__` -error[E0609]: no field `S1` on type `idleResources<'_>` - --> $DIR/resources-cfg.rs:38:21 +error[E0609]: no field `s1` on type `idleResources<'_>` + --> $DIR/resources-cfg.rs:56:21 | -38 | c.resources.S1; +56 | c.resources.s1; | ^^ unknown field | = note: available fields are: `__marker__` -error[E0609]: no field `S3` on type `idleResources<'_>` - --> $DIR/resources-cfg.rs:39:21 +error[E0609]: no field `s3` on type `idleResources<'_>` + --> $DIR/resources-cfg.rs:57:21 | -39 | c.resources.S3; +57 | c.resources.s3; | ^^ unknown field | = note: available fields are: `__marker__` -error[E0609]: no field `O3` on type `uart0Resources<'_>` - --> $DIR/resources-cfg.rs:46:21 +error[E0609]: no field `o3` on type `uart0Resources<'_>` + --> $DIR/resources-cfg.rs:64:21 | -46 | c.resources.O3; +64 | c.resources.o3; | ^^ unknown field | = note: available fields are: `__marker__` -error[E0609]: no field `S1` on type `uart0Resources<'_>` - --> $DIR/resources-cfg.rs:47:21 +error[E0609]: no field `s1` on type `uart0Resources<'_>` + --> $DIR/resources-cfg.rs:65:21 | -47 | c.resources.S1; +65 | c.resources.s1; | ^^ unknown field | = note: available fields are: `__marker__` -error[E0609]: no field `S2` on type `uart0Resources<'_>` - --> $DIR/resources-cfg.rs:48:21 +error[E0609]: no field `s2` on type `uart0Resources<'_>` + --> $DIR/resources-cfg.rs:66:21 | -48 | c.resources.S2; +66 | c.resources.s2; | ^^ unknown field | = note: available fields are: `__marker__` -error[E0609]: no field `S3` on type `uart0Resources<'_>` - --> $DIR/resources-cfg.rs:49:21 +error[E0609]: no field `s3` on type `uart0Resources<'_>` + --> $DIR/resources-cfg.rs:67:21 | -49 | c.resources.S3; +67 | c.resources.s3; | ^^ unknown field | = note: available fields are: `__marker__` -error[E0609]: no field `S2` on type `uart1Resources<'_>` - --> $DIR/resources-cfg.rs:54:21 +error[E0609]: no field `s2` on type `uart1Resources<'_>` + --> $DIR/resources-cfg.rs:72:21 | -54 | c.resources.S2; +72 | c.resources.s2; | ^^ unknown field | = note: available fields are: `__marker__` -error[E0609]: no field `O5` on type `uart1Resources<'_>` - --> $DIR/resources-cfg.rs:55:21 +error[E0609]: no field `o5` on type `uart1Resources<'_>` + --> $DIR/resources-cfg.rs:73:21 | -55 | c.resources.O5; +73 | c.resources.o5; | ^^ unknown field | = note: available fields are: `__marker__` -- cgit v1.2.3 From fb84029beef9bec3c205583296b181023f2e4b6b Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Thu, 11 Jul 2019 12:53:58 +0200 Subject: implement the #[shared] attribute as specified in RFC #211 --- macros/src/codegen/locals.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'macros') diff --git a/macros/src/codegen/locals.rs b/macros/src/codegen/locals.rs index 799ef7a..cbfe05f 100644 --- a/macros/src/codegen/locals.rs +++ b/macros/src/codegen/locals.rs @@ -42,7 +42,11 @@ pub fn codegen( let cfgs = &local.cfgs; has_cfgs |= !cfgs.is_empty(); - let section = util::link_section("data", core); + let section = if local.shared && cfg!(feature = "heterogeneous") { + Some(quote!(#[rtfm::export::shared])) + } else { + util::link_section("data", core) + }; let expr = &local.expr; let ty = &local.ty; fields.push(quote!( -- cgit v1.2.3 From 0e146f8d1142672725b6abb38478f503a9261c80 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Tue, 20 Aug 2019 15:11:24 +0200 Subject: adapt to changes in rtfm-syntax --- .travis.yml | 8 +++++++- macros/Cargo.toml | 6 +++--- macros/src/check.rs | 7 ++++--- macros/src/codegen.rs | 2 +- macros/src/codegen/hardware_tasks.rs | 1 + macros/src/codegen/idle.rs | 2 ++ macros/src/codegen/init.rs | 2 ++ macros/src/codegen/software_tasks.rs | 1 + macros/src/codegen/util.rs | 6 +++--- macros/src/lib.rs | 1 - tests/single.rs | 6 ++++-- 11 files changed, 28 insertions(+), 14 deletions(-) (limited to 'macros') diff --git a/.travis.yml b/.travis.yml index 31d10e8..ac5a7b8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,20 +5,26 @@ matrix: # NOTE used to build docs on successful merges to master - env: TARGET=x86_64-unknown-linux-gnu + # MSRV + - env: TARGET=thumbv7m-none-eabi + rust: 1.36.0 + if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) + - env: TARGET=thumbv6m-none-eabi if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) - env: TARGET=thumbv7m-none-eabi if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) + # compile-fail tests - env: TARGET=x86_64-unknown-linux-gnu rust: nightly if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) + # heterogeneous multi-core support - env: TARGET=thumbv6m-none-eabi rust: nightly if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) - - env: TARGET=thumbv7m-none-eabi rust: nightly if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) diff --git a/macros/Cargo.toml b/macros/Cargo.toml index c4e897f..ed7626f 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -15,9 +15,9 @@ version = "0.5.0-alpha.1" proc-macro = true [dependencies] -proc-macro2 = "0.4.30" -quote = "0.6.12" -syn = "0.15.34" +proc-macro2 = "1" +quote = "1" +syn = "1" [dependencies.rtfm-syntax] git = "https://github.com/japaric/rtfm-syntax" diff --git a/macros/src/check.rs b/macros/src/check.rs index 85fda75..0136370 100644 --- a/macros/src/check.rs +++ b/macros/src/check.rs @@ -169,9 +169,10 @@ pub fn app<'a>(app: &'a App, analysis: &Analysis) -> parse::Result> { peripherals = if *x { Some(0) } else { None } } - CustomArg::UInt(x) if app.args.cores != 1 => { - peripherals = if *x < u64::from(app.args.cores) { - Some(*x as u8) + CustomArg::UInt(s) if app.args.cores != 1 => { + let x = s.parse::().ok(); + peripherals = if x.is_some() && x.unwrap() < app.args.cores { + Some(x.unwrap()) } else { return Err(parse::Error::new( k.span(), diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs index 8ac06d5..0213848 100644 --- a/macros/src/codegen.rs +++ b/macros/src/codegen.rs @@ -126,7 +126,7 @@ pub fn app(app: &App, analysis: &Analysis, extra: &Extra) -> TokenStream2 { #(#root)* - #(#mod_resources)* + #mod_resources #(#root_hardware_tasks)* diff --git a/macros/src/codegen/hardware_tasks.rs b/macros/src/codegen/hardware_tasks.rs index cf92e07..a9c2a2b 100644 --- a/macros/src/codegen/hardware_tasks.rs +++ b/macros/src/codegen/hardware_tasks.rs @@ -115,6 +115,7 @@ pub fn codegen( let stmts = &task.stmts; let section = util::link_section("text", core); // XXX shouldn't this have a cfg_core? + let locals_pat = locals_pat.iter(); user_tasks.push(quote!( #(#attrs)* #[allow(non_snake_case)] diff --git a/macros/src/codegen/idle.rs b/macros/src/codegen/idle.rs index d656076..35a7252 100644 --- a/macros/src/codegen/idle.rs +++ b/macros/src/codegen/idle.rs @@ -58,6 +58,7 @@ pub fn codegen( let context = &idle.context; let stmts = &idle.stmts; let section = util::link_section("text", core); + let locals_pat = locals_pat.iter(); let user_idle = Some(quote!( #(#attrs)* #[allow(non_snake_case)] @@ -70,6 +71,7 @@ pub fn codegen( } )); + let locals_new = locals_new.iter(); let call_idle = quote!(#name( #(#locals_new,)* #name::Context::new(&rtfm::export::Priority::new(0)) diff --git a/macros/src/codegen/init.rs b/macros/src/codegen/init.rs index 878c633..9c8ce31 100644 --- a/macros/src/codegen/init.rs +++ b/macros/src/codegen/init.rs @@ -83,6 +83,7 @@ pub fn codegen( let attrs = &init.attrs; let stmts = &init.stmts; let section = util::link_section("text", core); + let locals_pat = locals_pat.iter(); let user_init = Some(quote!( #(#attrs)* #cfg_core @@ -102,6 +103,7 @@ pub fn codegen( const_app = Some(constructor); } + let locals_new = locals_new.iter(); let call_init = Some(quote!(let late = #name(#(#locals_new,)* #name::Context::new(core.into()));)); diff --git a/macros/src/codegen/software_tasks.rs b/macros/src/codegen/software_tasks.rs index 2960faf..be1eb05 100644 --- a/macros/src/codegen/software_tasks.rs +++ b/macros/src/codegen/software_tasks.rs @@ -168,6 +168,7 @@ pub fn codegen( let attrs = &task.attrs; let cfgs = &task.cfgs; let stmts = &task.stmts; + let locals_pat = locals_pat.iter(); user_tasks.push(quote!( #(#attrs)* #(#cfgs)* diff --git a/macros/src/codegen/util.rs b/macros/src/codegen/util.rs index f5f96de..207272d 100644 --- a/macros/src/codegen/util.rs +++ b/macros/src/codegen/util.rs @@ -3,13 +3,13 @@ use core::sync::atomic::{AtomicUsize, Ordering}; use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::quote; use rtfm_syntax::{ast::App, Context, Core}; -use syn::{ArgCaptured, Attribute, Ident, IntSuffix, LitInt}; +use syn::{Attribute, Ident, LitInt, PatType}; use crate::check::Extra; /// Turns `capacity` into an unsuffixed integer literal pub fn capacity_literal(capacity: u8) -> LitInt { - LitInt::new(u64::from(capacity), IntSuffix::None, Span::call_site()) + LitInt::new(&capacity.to_string(), Span::call_site()) } /// Turns `capacity` into a type-level (`typenum`) integer @@ -194,7 +194,7 @@ pub fn rendezvous_ident(core: Core) -> Ident { // // `inputs` could be &[`input: Foo`] OR &[`mut x: i32`, `ref y: i64`] pub fn regroup_inputs( - inputs: &[ArgCaptured], + inputs: &[PatType], ) -> ( // args e.g. &[`_0`], &[`_0: i32`, `_1: i64`] Vec, diff --git a/macros/src/lib.rs b/macros/src/lib.rs index ed55095..7a436e7 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -1,5 +1,4 @@ #![deny(warnings)] -#![recursion_limit = "128"] extern crate proc_macro; diff --git a/tests/single.rs b/tests/single.rs index 93addf6..01b8031 100644 --- a/tests/single.rs +++ b/tests/single.rs @@ -8,8 +8,10 @@ fn ui() { config.mode = Mode::Ui; config.src_base = PathBuf::from("ui/single"); - config.target_rustcflags = - Some("--edition=2018 -L target/debug/deps -Z unstable-options --extern rtfm --extern lm3s6965".to_owned()); + config.target_rustcflags = Some( + "--edition=2018 -L target/debug/deps -Z unstable-options --extern rtfm --extern lm3s6965" + .to_owned(), + ); config.link_deps(); config.clean_rmeta(); -- cgit v1.2.3 From 45f9faae9c51c8d84c939a9e00985388fc1d2cdf Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Wed, 21 Aug 2019 12:19:38 +0200 Subject: document #[app] --- Cargo.toml | 2 +- macros/src/lib.rs | 188 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 8 +-- 3 files changed, 192 insertions(+), 6 deletions(-) (limited to 'macros') diff --git a/Cargo.toml b/Cargo.toml index 5541717..1449d6e 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.5.0-alpha.1" +version = "0.5.0-beta.1" [lib] name = "rtfm" diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 7a436e7..6675765 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -13,6 +13,194 @@ mod codegen; #[cfg(test)] mod tests; +/// Attribute used to declare a RTFM application +/// +/// This attribute must be applied to a `const` item of type `()`. The `const` item is effectively +/// used as a `mod` item: its value must be a block that contains items commonly found in modules, +/// like functions and `static` variables. +/// +/// The `app` attribute has one mandatory argument: +/// +/// - `device = `. The path must point to a device crate generated using [`svd2rust`] +/// **v0.14.x**. +/// +/// [`svd2rust`]: https://crates.io/crates/svd2rust +/// +/// and several optional arguments: +/// +/// - `peripherals = `. Indicates whether the runtime takes the device peripherals and makes +/// them available to the `init` context. +/// +/// - `monotonic = `. This is a path to a zero-sized structure (e.g. `struct Foo;`) that +/// implements the `Monotonic` trait. This argument must be provided to use the `schedule` API. +/// +/// The items allowed in the block value of the `const` item are specified below: +/// +/// # 1. `struct Resources` +/// +/// This structure contains the declaration of all the resources used by the application. Each field +/// in this structure corresponds to a different resource. Each resource may optionally be given an +/// initial value using the `#[init()]` attribute. Resources with no compile-time initial +/// value as referred to as *late* resources. +/// +/// # 2. `fn` +/// +/// Functions must contain *one* of the following attributes: `init`, `idle` or `task`. The +/// attribute defines the role of the function in the application. +/// +/// ## a. `#[init]` +/// +/// This attribute indicates that the function is to be used as the *initialization function*. There +/// must be exactly one instance of the `init` attribute inside the `app` pseudo-module. The +/// signature of the `init` function must be `fn (::Context) [-> ::LateResources]` +/// where `` is the name of the function adorned with the `#[init]` attribute. +/// +/// The `init` function runs after memory (RAM) is initialized and runs with interrupts disabled. +/// Interrupts are re-enabled after `init` returns. +/// +/// The `init` attribute accepts the following optional arguments: +/// +/// - `resources = [resource_a, resource_b, ..]`. This is the list of resources this context has +/// access to. +/// +/// - `schedule = [task_a, task_b, ..]`. This is the list of *software* tasks that this context can +/// schedule to run in the future. *IMPORTANT*: This argument is accepted only if the `monotonic` +/// argument is passed to the `#[app]` attribute. +/// +/// - `spawn = [task_a, task_b, ..]`. This is the list of *software* tasks that this context can +/// immediately spawn. +/// +/// The first argument of the function, `::Context`, is a structure that contains the +/// following fields: +/// +/// - `core`. Exclusive access to core peripherals. The type of this field is [`rtfm::Peripherals`] +/// when the `schedule` API is used and [`cortex_m::Peripherals`] when it's not. +/// +/// [`rtfm::Peripherals`]: ../rtfm/struct.Peripherals.html +/// [`cortex_m::Peripherals`]: https://docs.rs/cortex-m/0.6/cortex_m/peripheral/struct.Peripherals.html +/// +/// - `device: ::Peripherals`. Exclusive access to device-specific peripherals. This +/// field is only present when the `peripherals` argument of the `#[app]` attribute is set to +/// `true`. `` is the path to the device crate specified in the top `app` attribute. +/// +/// - `start: `. The `start` time of the system: `::zero()`. `` is the +/// `Instant` type associated to the `Monotonic` implementation specified in the top `#[app]` +/// attribute. **NOTE**: this field is only present when the `schedule` is used. +/// +/// - `resources: ::Resources`. A `struct` that contains all the resources that can be +/// accessed from this context. Each field is a different resource; each resource may appear as a +/// reference (`&[mut]-`) or as proxy structure that implements the [`rftm::Mutex`] trait. +/// +/// [`rtfm::Mutex`]: ../rtfm/trait.Mutex.html +/// +/// - `schedule: ::Schedule`. A `struct` that can be used to schedule *software* tasks. +/// +/// - `spawn: ::Spawn`. A `struct` that can be used to spawn *software* tasks. +/// +/// The return type `::LateResources` must only be specified when late resources, resources +/// with no initial value declared at compile time, are used. `::LateResources` is a +/// structure where each field corresponds to a different late resource. The +/// `::LateResources` value returned by the `#[init]` function is used to initialize the +/// late resources before `idle` or any task can start. +/// +/// Other properties: +/// +/// - The `static mut` variables declared at the beginning of this function will be transformed into +/// `&'static mut` references that are safe to access. For example, `static mut FOO: u32 = 0` will +/// become `FOO: &'static mut u32`. +/// +/// ## b. `#[idle]` +/// +/// This attribute indicates that the function is to be used as the *idle task*. There can be at +/// most once instance of the `idle` attribute inside the `app` pseudo-module. The signature of the +/// `idle` function must be `fn(::Context) -> !` where `` is the name of the +/// function adorned with the `#[idle]` attribute. +/// +/// The `idle` task is a special task that always runs in the background. The `idle` task runs at +/// the lowest priority of `0`. If the `idle` task is not defined then the runtime sets the +/// [SLEEPONEXIT] bit after executing `init`. +/// +/// [SLEEPONEXIT]: https://developer.arm.com/products/architecture/cpu-architecture/m-profile/docs/100737/0100/power-management/sleep-mode/sleep-on-exit-bit +/// +/// The `idle` attribute accepts the following optional arguments: +/// +/// - `resources = (..)`. Same meaning / function as [`#[init].resources`](#a-init). +/// +/// - `schedule = (..)`. Same meaning / function as [`#[init].schedule`](#a-init). +/// +/// - `spawn = (..)`. Same meaning / function as [`#[init].spawn`](#a-init). +/// +/// The first argument of the function, `idle::Context`, is a structure that contains the following +/// fields: +/// +/// - `resources: _`. Same meaning / function as [`::Context.resources`](#a-init). +/// +/// - `schedule: idle::Schedule`. Same meaning / function as [`::Context.schedule`](#a-init). +/// +/// - `spawn: idle::Spawn`. Same meaning / function as [`::Context.spawn`](#a-init). +/// +/// Other properties: +/// +/// - The `static mut` variables declared at the beginning of this function will be transformed into +/// `&'static mut` references that are safe to access. For example, `static mut FOO: u32 = 0` will +/// become `FOO: &'static mut u32`. +/// +/// ## c. `#[task]` +/// +/// This attribute indicates that the function is either a hardware task or a software task. The +/// signature of hardware tasks must be `fn(::Context)` whereas the signature of software +/// tasks must be `fn(::Context, )`. `` refers to the name of the function +/// adorned with the `#[task]` attribute. +/// +/// The `task` attribute accepts the following optional arguments. +/// +/// - `binds = `. Binds this task to a particular interrupt. When this argument is +/// present the task is treated as a hardware task; when it's omitted the task treated is treated as +/// a software task. +/// +/// - `priority = `. This is the static priority of the exception handler. The value must +/// be in the range `1..=(1 << ::NVIC_PRIO_BITS)` where `` is the path to +/// the device crate specified in the top `app` attribute. If this argument is omitted the priority +/// is assumed to be 1. +/// +/// - `resources = (..)`. Same meaning / function as [`#[init].resources`](#a-init). +/// +/// - `schedule = (..)`. Same meaning / function as [`#[init].schedule`](#a-init). +/// +/// - `spawn = (..)`. Same meaning / function as [`#[init].spawn`](#a-init). +/// +/// The first argument of the function, `::Context`, is a structure that contains the +/// following fields: +/// +/// - `start: `. For hardware tasks this is the time at which this handler started +/// executing. For software tasks this is the time at which the task was scheduled to run. **NOTE**: +/// only present when the `schedule` API is used. +/// +/// - `resources: _`. Same meaning / function as [`::Context.resources`](#a-init). +/// +/// - `schedule: ::Schedule`. Same meaning / function as +/// [`::Context.schedule`](#a-init). +/// +/// - `spawn: ::Spawn`. Same meaning / function as +/// [`::Context.spawn`](#a-init). +/// +/// Other properties / constraints: +/// +/// - The `static mut` variables declared at the beginning of this function will be transformed into +/// *non*-static `&mut` references that are safe to access. For example, `static mut FOO: u32 = 0` +/// will become `FOO: &mut u32`. +/// +/// # 3. `extern` block +/// +/// This `extern` block contains a list of interrupts which are *not* used by the application as +/// hardware tasks. These interrupts will be used to dispatch software tasks. Each interrupt will be +/// used to dispatch *multiple* software tasks *at the same priority level*. +/// +/// This `extern` block must only contain functions with signature `fn ()`. The names of these +/// functions must match the names of the target device interrupts. +/// +/// Attributes can be applied to the functions inside this block. These attributes will be forwarded +/// to the interrupt handlers generated by the `app` attribute. #[proc_macro_attribute] pub fn app(args: TokenStream, input: TokenStream) -> TokenStream { let mut settings = Settings::default(); diff --git a/src/lib.rs b/src/lib.rs index 22eff5a..d78a1e1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ //! 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 +//! **HEADS UP** This is an **beta** 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 @@ -12,10 +12,8 @@ //! //! [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. -//! -//! [`#[app]`]: ../cortex_m_rtfm_macros/attr.app.html +//! Don't forget to check the documentation of the `#[app]` attribute (listed under the reexports +//! section), which is the main component of the framework. //! //! # Minimum Supported Rust Version (MSRV) //! -- cgit v1.2.3 From 996bdf8f0c18fe86f2649dd43f7019c4f6ad1bb2 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Wed, 21 Aug 2019 12:33:04 +0200 Subject: doc tweaks --- book/en/src/by-example/app.md | 4 ++-- macros/src/lib.rs | 4 ++-- src/lib.rs | 5 ++++- 3 files changed, 8 insertions(+), 5 deletions(-) (limited to 'macros') diff --git a/book/en/src/by-example/app.md b/book/en/src/by-example/app.md index 02c49b1..ebb71f1 100644 --- a/book/en/src/by-example/app.md +++ b/book/en/src/by-example/app.md @@ -10,8 +10,8 @@ All RTFM applications use the [`app`] attribute (`#[app(..)]`). This attribute must be applied to a `const` item that contains items. The `app` attribute has a mandatory `device` argument that takes a *path* as a value. This path must point to a *peripheral access crate* (PAC) generated using [`svd2rust`] -**v0.14.x**. The `app` attribute will expand into a suitable entry point so it's -not required to use the [`cortex_m_rt::entry`] attribute. +**v0.14.x** or newer. The `app` attribute will expand into a suitable entry +point so it's not required to use the [`cortex_m_rt::entry`] attribute. [`app`]: ../../api/cortex_m_rtfm_macros/attr.app.html [`svd2rust`]: https://crates.io/crates/svd2rust diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 6675765..fdde5c6 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -22,11 +22,11 @@ mod tests; /// The `app` attribute has one mandatory argument: /// /// - `device = `. The path must point to a device crate generated using [`svd2rust`] -/// **v0.14.x**. +/// **v0.14.x** or newer. /// /// [`svd2rust`]: https://crates.io/crates/svd2rust /// -/// and several optional arguments: +/// and a few optional arguments: /// /// - `peripherals = `. Indicates whether the runtime takes the device peripherals and makes /// them available to the `init` context. diff --git a/src/lib.rs b/src/lib.rs index d78a1e1..502cbc6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,7 +31,10 @@ //! //! # Cargo features //! -//! - `heterogeneous`. This opt-in feature enables the *experimental* heterogeneous multi-core support. +//! - `heterogeneous`. This opt-in feature enables the *experimental* heterogeneous multi-core +//! support. This feature depends on unstable feature and requires the use of the nightly channel. +//! +//! - `homogeneous`. This opt-in feature enables the *experimental* homogeneous multi-core support. #![deny(missing_docs)] #![deny(rust_2018_compatibility)] -- cgit v1.2.3 From 7aa270cb92180abfc9102a69efdde378c3396b5e Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Sun, 15 Sep 2019 18:36:00 +0200 Subject: don't use deprecated API --- macros/src/codegen/pre_init.rs | 2 +- src/export.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'macros') diff --git a/macros/src/codegen/pre_init.rs b/macros/src/codegen/pre_init.rs index 948dae5..605171b 100644 --- a/macros/src/codegen/pre_init.rs +++ b/macros/src/codegen/pre_init.rs @@ -75,7 +75,7 @@ pub fn codegen( // NOTE unmask the interrupt *after* setting its priority: changing the priority of a pended // interrupt is implementation defined - stmts.push(quote!(core.NVIC.enable(#device::#interrupt::#name);)); + stmts.push(quote!(rtfm::export::NVIC::unmask(#device::#interrupt::#name);)); } // cross-spawn barriers: now that priorities have been set and the interrupts have been unmasked diff --git a/src/export.rs b/src/export.rs index 572068c..96c444b 100644 --- a/src/export.rs +++ b/src/export.rs @@ -9,7 +9,7 @@ pub use cortex_m::register::basepri; pub use cortex_m::{ asm::wfi, interrupt, - peripheral::{scb::SystemHandler, syst::SystClkSource, DWT}, + peripheral::{scb::SystemHandler, syst::SystClkSource, DWT, NVIC}, Peripherals, }; use heapless::spsc::{MultiCore, SingleCore}; -- cgit v1.2.3