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/src/codegen.rs | 2467 ++----------------------------------------------- 1 file changed, 87 insertions(+), 2380 deletions(-) (limited to 'macros/src/codegen.rs') diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs index 88f11739..86b4a67e 100644 --- a/macros/src/codegen.rs +++ b/macros/src/codegen.rs @@ -1,136 +1,75 @@ -use proc_macro::TokenStream; -use std::collections::{BTreeMap, BTreeSet}; - -use proc_macro2::Span; +use proc_macro2::TokenStream as TokenStream2; use quote::quote; -use syn::{ArgCaptured, Attribute, Ident, IntSuffix, LitInt}; - -use crate::{ - analyze::{Analysis, Ownership}, - syntax::{App, Static}, -}; - -pub fn app(name: &Ident, app: &App, analysis: &Analysis) -> TokenStream { - let (const_app_resources, mod_resources) = resources(app, analysis); - - let ( - const_app_exceptions, - exception_mods, - exception_locals, - exception_resources, - user_exceptions, - ) = exceptions(app, analysis); - - let ( - const_app_interrupts, - interrupt_mods, - interrupt_locals, - interrupt_resources, - user_interrupts, - ) = interrupts(app, analysis); - - let (const_app_tasks, task_mods, task_locals, task_resources, user_tasks) = - tasks(app, analysis); - - let const_app_dispatchers = dispatchers(&app, analysis); - - let const_app_spawn = spawn(app, analysis); - - let const_app_tq = timer_queue(app, analysis); - - let const_app_schedule = schedule(app); - - let assertion_stmts = assertions(app, analysis); - - let pre_init_stmts = pre_init(&app, analysis); - - let ( - const_app_init, - mod_init, - init_locals, - init_resources, - init_late_resources, - user_init, - call_init, - ) = init(app, analysis); - - let post_init_stmts = post_init(&app, analysis); - - let (const_app_idle, mod_idle, idle_locals, idle_resources, user_idle, call_idle) = - idle(app, analysis); - - let device = &app.args.device; - quote!( - #user_init - - #user_idle - - #(#user_exceptions)* - - #(#user_interrupts)* - - #(#user_tasks)* - - #mod_resources - - #init_locals - - #init_resources - - #init_late_resources - - #mod_init - - #idle_locals - - #idle_resources - - #mod_idle - - #(#exception_locals)* +use rtfm_syntax::ast::App; + +use crate::{analyze::Analysis, check::Extra}; + +mod assertions; +mod dispatchers; +mod hardware_tasks; +mod idle; +mod init; +mod locals; +mod module; +mod post_init; +mod pre_init; +mod resources; +mod resources_struct; +mod schedule; +mod schedule_body; +mod software_tasks; +mod spawn; +mod spawn_body; +mod timer_queue; +mod util; + +// TODO document the syntax here or in `rtfm-syntax` +pub fn app(app: &App, analysis: &Analysis, extra: &Extra) -> TokenStream2 { + let mut const_app = vec![]; + let mut mains = vec![]; + let mut root = vec![]; + let mut user = vec![]; - #(#exception_resources)* + // generate a `main` function for each core + for core in 0..app.args.cores { + let assertion_stmts = assertions::codegen(core, analysis); - #(#exception_mods)* + let (const_app_pre_init, pre_init_stmts) = pre_init::codegen(core, &app, analysis, extra); - #(#interrupt_locals)* + let (const_app_init, root_init, user_init, call_init) = + init::codegen(core, app, analysis, extra); - #(#interrupt_resources)* + let (const_app_post_init, post_init_stmts) = post_init::codegen(core, analysis, extra); - #(#interrupt_mods)* + let (const_app_idle, root_idle, user_idle, call_idle) = + idle::codegen(core, app, analysis, extra); - #(#task_locals)* + user.push(quote!( + #user_init - #(#task_resources)* + #user_idle + )); - #(#task_mods)* + root.push(quote!( + #(#root_init)* - /// Implementation details - const #name: () = { - // always include the device crate, which contains the vector table - use #device as _; + #(#root_idle)* + )); - #(#const_app_resources)* + const_app.push(quote!( + #(#const_app_pre_init)* #const_app_init - #const_app_idle - - #(#const_app_exceptions)* - - #(#const_app_interrupts)* - - #(#const_app_dispatchers)* - - #(#const_app_tasks)* - - #(#const_app_spawn)* - - #(#const_app_tq)* + #(#const_app_post_init)* - #(#const_app_schedule)* + #const_app_idle + )); + let cfg_core = util::cfg_core(core, app.args.cores); + mains.push(quote!( #[no_mangle] + #cfg_core unsafe fn main() -> ! { #(#assertion_stmts)* @@ -142,2297 +81,65 @@ pub fn app(name: &Ident, app: &App, analysis: &Analysis) -> TokenStream { #call_idle } - }; - ) - .into() -} - -/* Main functions */ -/// In this pass we generate a static variable and a resource proxy for each resource -/// -/// If the user specified a resource like this: -/// -/// ``` -/// #[rtfm::app(device = ..)] -/// const APP: () = { -/// static mut X: UserDefinedStruct = (); -/// static mut Y: u64 = 0; -/// static mut Z: u32 = 0; -/// } -/// ``` -/// -/// We'll generate code like this: -/// -/// - `const_app` -/// -/// ``` -/// const APP: () = { -/// static mut X: MaybeUninit = 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, - } - } -} -- 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/src/codegen.rs') diff --git a/Cargo.toml b/Cargo.toml index 81ca256c..ef45be85 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 a6485cf7..1b3d5615 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 00000000..fd05d07e --- /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 00000000..8e49ff8b --- /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 00000000..9b6bb82d --- /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 00000000..b9c39197 --- /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 00000000..53e73805 --- /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 00000000..cbfc01f9 --- /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 00000000..3fc64f6f --- /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 00000000..a4f0ec57 --- /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 00000000..210ee2e8 --- /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 00000000..17e9c6e1 --- /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 00000000..b99476c7 --- /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 00000000..f51e2f6e --- /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 00000000..5089e385 --- /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 00000000..12b5cb80 --- /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 00000000..a76ac61c --- /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 00000000..a4f0ec57 --- /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 2854dad4..c4e897fa 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 c22a0f1f..619ec8fb 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 86b4a67e..92766260 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 65d25c78..988e3c84 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 8b2c0cd5..383a5d82 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 83cb5c0a..98bce074 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 cb845774..d306ed5b 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 203fcee8..8c43b350 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 6e1a7978..6502d9ca 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 7c75335d..00000000 --- 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 e1335bbf..00000000 --- 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 792935a8..00000000 --- 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 ff48b110..00000000 --- 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 3f26c5c9..00000000 --- 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 76e70acf..00000000 --- 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 749918fd..00000000 --- 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 d86c0e8e..00000000 --- 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 73e6e200..acb3a63d 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 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/src/codegen.rs') diff --git a/heterogeneous/src/lib.rs b/heterogeneous/src/lib.rs index a4f0ec57..3288bfe0 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 a4f0ec57..3288bfe0 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 92766260..a3515994 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 95268a2c..4a77352f 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 a2b216c1..468aa712 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 7646e3c5..572068ce 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 acb3a63d..decd2da1 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/src/codegen.rs') diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs index a3515994..8a548323 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/src/codegen.rs') diff --git a/build.rs b/build.rs index 2419b4eb..14c3d248 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 8a548323..8ac06d53 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 988e3c84..9a9cb102 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 271be94c..878c633e 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 96635637..799ef7a0 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 2425681b..1161a7a5 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 57f01a2c..8cf60985 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 383a5d82..2960faf9 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 1539e277..c63c410b 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 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/src/codegen.rs') diff --git a/.travis.yml b/.travis.yml index 31d10e84..ac5a7b8a 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 c4e897fa..ed7626f8 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 85fda75b..0136370c 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 8ac06d53..02138481 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 cf92e078..a9c2a2bd 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 d6560761..35a72523 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 878c633e..9c8ce31c 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 2960faf9..be1eb05c 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 f5f96dea..207272dc 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 ed55095d..7a436e7b 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 93addf6e..01b80312 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