diff options
Diffstat (limited to 'rtic-macros/src/codegen')
-rw-r--r-- | rtic-macros/src/codegen/assertions.rs | 53 | ||||
-rw-r--r-- | rtic-macros/src/codegen/async_dispatchers.rs | 89 | ||||
-rw-r--r-- | rtic-macros/src/codegen/hardware_tasks.rs | 87 | ||||
-rw-r--r-- | rtic-macros/src/codegen/idle.rs | 58 | ||||
-rw-r--r-- | rtic-macros/src/codegen/init.rs | 95 | ||||
-rw-r--r-- | rtic-macros/src/codegen/local_resources.rs | 65 | ||||
-rw-r--r-- | rtic-macros/src/codegen/local_resources_struct.rs | 102 | ||||
-rw-r--r-- | rtic-macros/src/codegen/main.rs | 52 | ||||
-rw-r--r-- | rtic-macros/src/codegen/module.rs | 197 | ||||
-rw-r--r-- | rtic-macros/src/codegen/post_init.rs | 47 | ||||
-rw-r--r-- | rtic-macros/src/codegen/pre_init.rs | 85 | ||||
-rw-r--r-- | rtic-macros/src/codegen/shared_resources.rs | 183 | ||||
-rw-r--r-- | rtic-macros/src/codegen/shared_resources_struct.rs | 119 | ||||
-rw-r--r-- | rtic-macros/src/codegen/software_tasks.rs | 64 | ||||
-rw-r--r-- | rtic-macros/src/codegen/util.rs | 238 |
15 files changed, 1534 insertions, 0 deletions
diff --git a/rtic-macros/src/codegen/assertions.rs b/rtic-macros/src/codegen/assertions.rs new file mode 100644 index 00000000..dd94aa6d --- /dev/null +++ b/rtic-macros/src/codegen/assertions.rs @@ -0,0 +1,53 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +use crate::syntax::ast::App; +use crate::{analyze::Analysis, codegen::util}; + +/// Generates compile-time assertions that check that types implement the `Send` / `Sync` traits +pub fn codegen(app: &App, analysis: &Analysis) -> Vec<TokenStream2> { + let mut stmts = vec![]; + + for ty in &analysis.send_types { + stmts.push(quote!(rtic::export::assert_send::<#ty>();)); + } + + for ty in &analysis.sync_types { + stmts.push(quote!(rtic::export::assert_sync::<#ty>();)); + } + + let device = &app.args.device; + let chunks_name = util::priority_mask_chunks_ident(); + let no_basepri_checks: Vec<_> = app + .hardware_tasks + .iter() + .filter_map(|(_, task)| { + if !util::is_exception(&task.args.binds) { + let interrupt_name = &task.args.binds; + Some(quote!( + if (#device::Interrupt::#interrupt_name as usize) >= (#chunks_name * 32) { + ::core::panic!("An interrupt out of range is used while in armv6 or armv8m.base"); + } + )) + } else { + None + } + }) + .collect(); + + let const_check = quote! { + const _CONST_CHECK: () = { + if !rtic::export::have_basepri() { + #(#no_basepri_checks)* + } else { + // TODO: Add armv7 checks here + } + }; + + let _ = _CONST_CHECK; + }; + + stmts.push(const_check); + + stmts +} diff --git a/rtic-macros/src/codegen/async_dispatchers.rs b/rtic-macros/src/codegen/async_dispatchers.rs new file mode 100644 index 00000000..a12ad325 --- /dev/null +++ b/rtic-macros/src/codegen/async_dispatchers.rs @@ -0,0 +1,89 @@ +use crate::syntax::ast::App; +use crate::{analyze::Analysis, codegen::util}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +/// Generates task dispatchers +pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { + let mut items = vec![]; + + let interrupts = &analysis.interrupts; + + // Generate executor definition and priority in global scope + for (name, _) in app.software_tasks.iter() { + let type_name = util::internal_task_ident(name, "F"); + let exec_name = util::internal_task_ident(name, "EXEC"); + + items.push(quote!( + #[allow(non_camel_case_types)] + type #type_name = impl core::future::Future; + #[allow(non_upper_case_globals)] + static #exec_name: rtic::export::executor::AsyncTaskExecutor<#type_name> = + rtic::export::executor::AsyncTaskExecutor::new(); + )); + } + + for (&level, channel) in &analysis.channels { + let mut stmts = vec![]; + + let dispatcher_name = if level > 0 { + util::suffixed(&interrupts.get(&level).expect("UNREACHABLE").0.to_string()) + } else { + util::zero_prio_dispatcher_ident() + }; + + let pend_interrupt = if level > 0 { + let device = &app.args.device; + let enum_ = util::interrupt_ident(); + + quote!(rtic::pend(#device::#enum_::#dispatcher_name);) + } else { + // For 0 priority tasks we don't need to pend anything + quote!() + }; + + for name in channel.tasks.iter() { + let exec_name = util::internal_task_ident(name, "EXEC"); + // TODO: Fix cfg + // let task = &app.software_tasks[name]; + // let cfgs = &task.cfgs; + + stmts.push(quote!( + #exec_name.poll(|| { + #exec_name.set_pending(); + #pend_interrupt + }); + )); + } + + if level > 0 { + let doc = format!("Interrupt handler to dispatch async tasks at priority {level}"); + let attribute = &interrupts.get(&level).expect("UNREACHABLE").1.attrs; + items.push(quote!( + #[allow(non_snake_case)] + #[doc = #doc] + #[no_mangle] + #(#attribute)* + unsafe fn #dispatcher_name() { + /// The priority of this interrupt handler + const PRIORITY: u8 = #level; + + rtic::export::run(PRIORITY, || { + #(#stmts)* + }); + } + )); + } else { + items.push(quote!( + #[allow(non_snake_case)] + unsafe fn #dispatcher_name() -> ! { + loop { + #(#stmts)* + } + } + )); + } + } + + quote!(#(#items)*) +} diff --git a/rtic-macros/src/codegen/hardware_tasks.rs b/rtic-macros/src/codegen/hardware_tasks.rs new file mode 100644 index 00000000..8a5a8f6c --- /dev/null +++ b/rtic-macros/src/codegen/hardware_tasks.rs @@ -0,0 +1,87 @@ +use crate::syntax::{ast::App, Context}; +use crate::{ + analyze::Analysis, + codegen::{local_resources_struct, module, shared_resources_struct}, +}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +/// Generate support code for hardware tasks (`#[exception]`s and `#[interrupt]`s) +pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { + let mut mod_app = vec![]; + let mut root = vec![]; + let mut user_tasks = vec![]; + + for (name, task) in &app.hardware_tasks { + let symbol = task.args.binds.clone(); + let priority = task.args.priority; + let cfgs = &task.cfgs; + let attrs = &task.attrs; + + mod_app.push(quote!( + #[allow(non_snake_case)] + #[no_mangle] + #(#attrs)* + #(#cfgs)* + unsafe fn #symbol() { + const PRIORITY: u8 = #priority; + + rtic::export::run(PRIORITY, || { + #name( + #name::Context::new() + ) + }); + } + )); + + // `${task}Locals` + if !task.args.local_resources.is_empty() { + let (item, constructor) = + local_resources_struct::codegen(Context::HardwareTask(name), app); + + root.push(item); + + mod_app.push(constructor); + } + + // `${task}Resources` + if !task.args.shared_resources.is_empty() { + let (item, constructor) = + shared_resources_struct::codegen(Context::HardwareTask(name), app); + + root.push(item); + + mod_app.push(constructor); + } + + // Module generation... + + root.push(module::codegen(Context::HardwareTask(name), app, analysis)); + + // End module generation + + if !task.is_extern { + let attrs = &task.attrs; + let context = &task.context; + let stmts = &task.stmts; + user_tasks.push(quote!( + #(#attrs)* + #[allow(non_snake_case)] + fn #name(#context: #name::Context) { + use rtic::Mutex as _; + use rtic::mutex::prelude::*; + + #(#stmts)* + } + )); + } + } + + quote!( + #(#mod_app)* + + #(#root)* + + #(#user_tasks)* + ) +} diff --git a/rtic-macros/src/codegen/idle.rs b/rtic-macros/src/codegen/idle.rs new file mode 100644 index 00000000..0c833ef3 --- /dev/null +++ b/rtic-macros/src/codegen/idle.rs @@ -0,0 +1,58 @@ +use crate::syntax::{ast::App, Context}; +use crate::{ + analyze::Analysis, + codegen::{local_resources_struct, module, shared_resources_struct}, +}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +/// Generates support code for `#[idle]` functions +pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { + if let Some(idle) = &app.idle { + let mut mod_app = vec![]; + let mut root_idle = vec![]; + + let name = &idle.name; + + if !idle.args.shared_resources.is_empty() { + let (item, constructor) = shared_resources_struct::codegen(Context::Idle, app); + + root_idle.push(item); + mod_app.push(constructor); + } + + if !idle.args.local_resources.is_empty() { + let (item, constructor) = local_resources_struct::codegen(Context::Idle, app); + + root_idle.push(item); + + mod_app.push(constructor); + } + + root_idle.push(module::codegen(Context::Idle, app, analysis)); + + let attrs = &idle.attrs; + let context = &idle.context; + let stmts = &idle.stmts; + let user_idle = Some(quote!( + #(#attrs)* + #[allow(non_snake_case)] + fn #name(#context: #name::Context) -> ! { + use rtic::Mutex as _; + use rtic::mutex::prelude::*; + + #(#stmts)* + } + )); + + quote!( + #(#mod_app)* + + #(#root_idle)* + + #user_idle + ) + } else { + quote!() + } +} diff --git a/rtic-macros/src/codegen/init.rs b/rtic-macros/src/codegen/init.rs new file mode 100644 index 00000000..6e1059f7 --- /dev/null +++ b/rtic-macros/src/codegen/init.rs @@ -0,0 +1,95 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +use crate::{ + analyze::Analysis, + codegen::{local_resources_struct, module}, + syntax::{ast::App, Context}, +}; + +/// Generates support code for `#[init]` functions +pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { + let init = &app.init; + let name = &init.name; + + let mut root_init = vec![]; + + let context = &init.context; + let attrs = &init.attrs; + let stmts = &init.stmts; + let shared = &init.user_shared_struct; + let local = &init.user_local_struct; + + let shared_resources: Vec<_> = app + .shared_resources + .iter() + .map(|(k, v)| { + let ty = &v.ty; + let cfgs = &v.cfgs; + let docs = &v.docs; + quote!( + #(#cfgs)* + #(#docs)* + #k: #ty, + ) + }) + .collect(); + let local_resources: Vec<_> = app + .local_resources + .iter() + .map(|(k, v)| { + let ty = &v.ty; + let cfgs = &v.cfgs; + let docs = &v.docs; + quote!( + #(#cfgs)* + #(#docs)* + #k: #ty, + ) + }) + .collect(); + + root_init.push(quote! { + struct #shared { + #(#shared_resources)* + } + + struct #local { + #(#local_resources)* + } + }); + + // let locals_pat = locals_pat.iter(); + + let user_init_return = quote! {#shared, #local}; + + let user_init = quote!( + #(#attrs)* + #[inline(always)] + #[allow(non_snake_case)] + fn #name(#context: #name::Context) -> (#user_init_return) { + #(#stmts)* + } + ); + + let mut mod_app = None; + + // `${task}Locals` + if !init.args.local_resources.is_empty() { + let (item, constructor) = local_resources_struct::codegen(Context::Init, app); + + root_init.push(item); + + mod_app = Some(constructor); + } + + root_init.push(module::codegen(Context::Init, app, analysis)); + + quote!( + #mod_app + + #(#root_init)* + + #user_init + ) +} diff --git a/rtic-macros/src/codegen/local_resources.rs b/rtic-macros/src/codegen/local_resources.rs new file mode 100644 index 00000000..e6d15533 --- /dev/null +++ b/rtic-macros/src/codegen/local_resources.rs @@ -0,0 +1,65 @@ +use crate::syntax::ast::App; +use crate::{analyze::Analysis, codegen::util}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +/// Generates `local` variables and local resource proxies +/// +/// I.e. the `static` variables and theirs proxies. +pub fn codegen(app: &App, _analysis: &Analysis) -> TokenStream2 { + let mut mod_app = vec![]; + + // All local resources declared in the `#[local]' struct + for (name, res) in &app.local_resources { + let cfgs = &res.cfgs; + let ty = &res.ty; + let mangled_name = util::static_local_resource_ident(name); + + let attrs = &res.attrs; + + // late resources in `util::link_section_uninit` + // unless user specifies custom link section + let section = if attrs.iter().any(|attr| attr.path.is_ident("link_section")) { + None + } else { + Some(util::link_section_uninit()) + }; + + // For future use + // let doc = format!(" RTIC internal: {}:{}", file!(), line!()); + mod_app.push(quote!( + #[allow(non_camel_case_types)] + #[allow(non_upper_case_globals)] + // #[doc = #doc] + #[doc(hidden)] + #(#attrs)* + #(#cfgs)* + #section + static #mangled_name: rtic::RacyCell<core::mem::MaybeUninit<#ty>> = rtic::RacyCell::new(core::mem::MaybeUninit::uninit()); + )); + } + + // All declared `local = [NAME: TY = EXPR]` local resources + for (task_name, resource_name, task_local) in app.declared_local_resources() { + let cfgs = &task_local.cfgs; + let ty = &task_local.ty; + let expr = &task_local.expr; + let attrs = &task_local.attrs; + + let mangled_name = util::declared_static_local_resource_ident(resource_name, task_name); + + // For future use + // let doc = format!(" RTIC internal: {}:{}", file!(), line!()); + mod_app.push(quote!( + #[allow(non_camel_case_types)] + #[allow(non_upper_case_globals)] + // #[doc = #doc] + #[doc(hidden)] + #(#attrs)* + #(#cfgs)* + static #mangled_name: rtic::RacyCell<#ty> = rtic::RacyCell::new(#expr); + )); + } + + quote!(#(#mod_app)*) +} diff --git a/rtic-macros/src/codegen/local_resources_struct.rs b/rtic-macros/src/codegen/local_resources_struct.rs new file mode 100644 index 00000000..100c3eb5 --- /dev/null +++ b/rtic-macros/src/codegen/local_resources_struct.rs @@ -0,0 +1,102 @@ +use crate::syntax::{ + ast::{App, TaskLocal}, + Context, +}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +use crate::codegen::util; + +/// Generates local resources structs +pub fn codegen(ctxt: Context, app: &App) -> (TokenStream2, TokenStream2) { + let resources = match ctxt { + Context::Init => &app.init.args.local_resources, + Context::Idle => { + &app.idle + .as_ref() + .expect("RTIC-ICE: unable to get idle name") + .args + .local_resources + } + Context::HardwareTask(name) => &app.hardware_tasks[name].args.local_resources, + Context::SoftwareTask(name) => &app.software_tasks[name].args.local_resources, + }; + + let task_name = util::get_task_name(ctxt, app); + + let mut fields = vec![]; + let mut values = vec![]; + + for (name, task_local) in resources { + let (cfgs, ty, is_declared) = match task_local { + TaskLocal::External => { + let r = app.local_resources.get(name).expect("UNREACHABLE"); + (&r.cfgs, &r.ty, false) + } + TaskLocal::Declared(r) => (&r.cfgs, &r.ty, true), + }; + + let lt = if ctxt.runs_once() { + quote!('static) + } else { + quote!('a) + }; + + let mangled_name = if matches!(task_local, TaskLocal::External) { + util::static_local_resource_ident(name) + } else { + util::declared_static_local_resource_ident(name, &task_name) + }; + + fields.push(quote!( + #(#cfgs)* + #[allow(missing_docs)] + pub #name: &#lt mut #ty + )); + + let expr = if is_declared { + // If the local resources is already initialized, we only need to access its value and + // not go through an `MaybeUninit` + quote!(&mut *#mangled_name.get_mut()) + } else { + quote!(&mut *(&mut *#mangled_name.get_mut()).as_mut_ptr()) + }; + + values.push(quote!( + #(#cfgs)* + #name: #expr + )); + } + + fields.push(quote!( + #[doc(hidden)] + pub __rtic_internal_marker: ::core::marker::PhantomData<&'a ()> + )); + + values.push(quote!(__rtic_internal_marker: ::core::marker::PhantomData)); + + let doc = format!("Local resources `{}` has access to", ctxt.ident(app)); + let ident = util::local_resources_ident(ctxt, app); + let item = quote!( + #[allow(non_snake_case)] + #[allow(non_camel_case_types)] + #[doc = #doc] + pub struct #ident<'a> { + #(#fields,)* + } + ); + + let constructor = quote!( + impl<'a> #ident<'a> { + #[inline(always)] + #[allow(missing_docs)] + pub unsafe fn new() -> Self { + #ident { + #(#values,)* + } + } + } + ); + + (item, constructor) +} diff --git a/rtic-macros/src/codegen/main.rs b/rtic-macros/src/codegen/main.rs new file mode 100644 index 00000000..2775d259 --- /dev/null +++ b/rtic-macros/src/codegen/main.rs @@ -0,0 +1,52 @@ +use crate::{analyze::Analysis, codegen::util, syntax::ast::App}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +use super::{assertions, post_init, pre_init}; + +/// Generates code for `fn main` +pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { + let assertion_stmts = assertions::codegen(app, analysis); + + let pre_init_stmts = pre_init::codegen(app, analysis); + + let post_init_stmts = post_init::codegen(app, analysis); + + let call_idle = if let Some(idle) = &app.idle { + let name = &idle.name; + quote!(#name(#name::Context::new())) + } else if analysis.channels.get(&0).is_some() { + let dispatcher = util::zero_prio_dispatcher_ident(); + quote!(#dispatcher();) + } else { + quote!(loop { + rtic::export::nop() + }) + }; + + let main = util::suffixed("main"); + let init_name = &app.init.name; + quote!( + #[doc(hidden)] + #[no_mangle] + unsafe extern "C" fn #main() -> ! { + #(#assertion_stmts)* + + #(#pre_init_stmts)* + + #[inline(never)] + fn __rtic_init_resources<F>(f: F) where F: FnOnce() { + f(); + } + + // Wrap late_init_stmts in a function to ensure that stack space is reclaimed. + __rtic_init_resources(||{ + let (shared_resources, local_resources) = #init_name(#init_name::Context::new(core.into())); + + #(#post_init_stmts)* + }); + + #call_idle + } + ) +} diff --git a/rtic-macros/src/codegen/module.rs b/rtic-macros/src/codegen/module.rs new file mode 100644 index 00000000..8b3fca23 --- /dev/null +++ b/rtic-macros/src/codegen/module.rs @@ -0,0 +1,197 @@ +use crate::syntax::{ast::App, Context}; +use crate::{analyze::Analysis, codegen::util}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +#[allow(clippy::too_many_lines)] +pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 { + let mut items = vec![]; + let mut module_items = vec![]; + let mut fields = vec![]; + let mut values = vec![]; + // Used to copy task cfgs to the whole module + let mut task_cfgs = vec![]; + + let name = ctxt.ident(app); + + match ctxt { + Context::Init => { + fields.push(quote!( + /// Core (Cortex-M) peripherals + pub core: rtic::export::Peripherals + )); + + if app.args.peripherals { + let device = &app.args.device; + + fields.push(quote!( + /// Device peripherals + pub device: #device::Peripherals + )); + + values.push(quote!(device: #device::Peripherals::steal())); + } + + fields.push(quote!( + /// Critical section token for init + pub cs: rtic::export::CriticalSection<'a> + )); + + values.push(quote!(cs: rtic::export::CriticalSection::new())); + + values.push(quote!(core)); + } + + Context::Idle | Context::HardwareTask(_) | Context::SoftwareTask(_) => {} + } + + if ctxt.has_local_resources(app) { + let ident = util::local_resources_ident(ctxt, app); + + module_items.push(quote!( + #[doc(inline)] + pub use super::#ident as LocalResources; + )); + + fields.push(quote!( + /// Local Resources this task has access to + pub local: #name::LocalResources<'a> + )); + + values.push(quote!(local: #name::LocalResources::new())); + } + + if ctxt.has_shared_resources(app) { + let ident = util::shared_resources_ident(ctxt, app); + + module_items.push(quote!( + #[doc(inline)] + pub use super::#ident as SharedResources; + )); + + fields.push(quote!( + /// Shared Resources this task has access to + pub shared: #name::SharedResources<'a> + )); + + values.push(quote!(shared: #name::SharedResources::new())); + } + + let doc = match ctxt { + Context::Idle => "Idle loop", + Context::Init => "Initialization function", + Context::HardwareTask(_) => "Hardware task", + Context::SoftwareTask(_) => "Software task", + }; + + let v = Vec::new(); + let cfgs = match ctxt { + Context::HardwareTask(t) => &app.hardware_tasks[t].cfgs, + Context::SoftwareTask(t) => &app.software_tasks[t].cfgs, + _ => &v, + }; + + let core = if ctxt.is_init() { + Some(quote!(core: rtic::export::Peripherals,)) + } else { + None + }; + + let internal_context_name = util::internal_task_ident(name, "Context"); + let exec_name = util::internal_task_ident(name, "EXEC"); + + items.push(quote!( + #(#cfgs)* + /// Execution context + #[allow(non_snake_case)] + #[allow(non_camel_case_types)] + pub struct #internal_context_name<'a> { + #[doc(hidden)] + __rtic_internal_p: ::core::marker::PhantomData<&'a ()>, + #(#fields,)* + } + + #(#cfgs)* + impl<'a> #internal_context_name<'a> { + #[inline(always)] + #[allow(missing_docs)] + pub unsafe fn new(#core) -> Self { + #internal_context_name { + __rtic_internal_p: ::core::marker::PhantomData, + #(#values,)* + } + } + } + )); + + module_items.push(quote!( + #(#cfgs)* + #[doc(inline)] + pub use super::#internal_context_name as Context; + )); + + if let Context::SoftwareTask(..) = ctxt { + let spawnee = &app.software_tasks[name]; + let priority = spawnee.args.priority; + let cfgs = &spawnee.cfgs; + // Store a copy of the task cfgs + task_cfgs = cfgs.clone(); + + let pend_interrupt = if priority > 0 { + let device = &app.args.device; + let enum_ = util::interrupt_ident(); + let interrupt = &analysis.interrupts.get(&priority).expect("UREACHABLE").0; + quote!(rtic::pend(#device::#enum_::#interrupt);) + } else { + quote!() + }; + + let internal_spawn_ident = util::internal_task_ident(name, "spawn"); + let (input_args, input_tupled, input_untupled, input_ty) = + util::regroup_inputs(&spawnee.inputs); + + // Spawn caller + items.push(quote!( + #(#cfgs)* + /// Spawns the task directly + #[allow(non_snake_case)] + #[doc(hidden)] + pub fn #internal_spawn_ident(#(#input_args,)*) -> Result<(), #input_ty> { + // SAFETY: If `try_allocate` suceeds one must call `spawn`, which we do. + unsafe { + if #exec_name.try_allocate() { + let f = #name(unsafe { #name::Context::new() } #(,#input_untupled)*); + #exec_name.spawn(f); + #pend_interrupt + + Ok(()) + } else { + Err(#input_tupled) + } + } + + } + )); + + module_items.push(quote!( + #(#cfgs)* + #[doc(inline)] + pub use super::#internal_spawn_ident as spawn; + )); + } + + if items.is_empty() { + quote!() + } else { + quote!( + #(#items)* + + #[allow(non_snake_case)] + #(#task_cfgs)* + #[doc = #doc] + pub mod #name { + #(#module_items)* + } + ) + } +} diff --git a/rtic-macros/src/codegen/post_init.rs b/rtic-macros/src/codegen/post_init.rs new file mode 100644 index 00000000..c4e53837 --- /dev/null +++ b/rtic-macros/src/codegen/post_init.rs @@ -0,0 +1,47 @@ +use crate::{analyze::Analysis, codegen::util, syntax::ast::App}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +/// Generates code that runs after `#[init]` returns +pub fn codegen(app: &App, analysis: &Analysis) -> Vec<TokenStream2> { + let mut stmts = vec![]; + + // Initialize shared resources + for (name, res) in &app.shared_resources { + let mangled_name = util::static_shared_resource_ident(name); + // If it's live + let cfgs = res.cfgs.clone(); + if analysis.shared_resources.get(name).is_some() { + stmts.push(quote!( + // We include the cfgs + #(#cfgs)* + // Resource is a RacyCell<MaybeUninit<T>> + // - `get_mut` to obtain a raw pointer to `MaybeUninit<T>` + // - `write` the defined value for the late resource T + #mangled_name.get_mut().write(core::mem::MaybeUninit::new(shared_resources.#name)); + )); + } + } + + // Initialize local resources + for (name, res) in &app.local_resources { + let mangled_name = util::static_local_resource_ident(name); + // If it's live + let cfgs = res.cfgs.clone(); + if analysis.local_resources.get(name).is_some() { + stmts.push(quote!( + // We include the cfgs + #(#cfgs)* + // Resource is a RacyCell<MaybeUninit<T>> + // - `get_mut` to obtain a raw pointer to `MaybeUninit<T>` + // - `write` the defined value for the late resource T + #mangled_name.get_mut().write(core::mem::MaybeUninit::new(local_resources.#name)); + )); + } + } + + // Enable the interrupts -- this completes the `init`-ialization phase + stmts.push(quote!(rtic::export::interrupt::enable();)); + + stmts +} diff --git a/rtic-macros/src/codegen/pre_init.rs b/rtic-macros/src/codegen/pre_init.rs new file mode 100644 index 00000000..28ba29c0 --- /dev/null +++ b/rtic-macros/src/codegen/pre_init.rs @@ -0,0 +1,85 @@ +use crate::syntax::ast::App; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +use crate::{analyze::Analysis, codegen::util}; + +/// Generates code that runs before `#[init]` +pub fn codegen(app: &App, analysis: &Analysis) -> Vec<TokenStream2> { + let mut stmts = vec![]; + + let rt_err = util::rt_err_ident(); + + // Disable interrupts -- `init` must run with interrupts disabled + stmts.push(quote!(rtic::export::interrupt::disable();)); + + stmts.push(quote!( + // To set the variable in cortex_m so the peripherals cannot be taken multiple times + let mut core: rtic::export::Peripherals = rtic::export::Peripherals::steal().into(); + )); + + let device = &app.args.device; + let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS); + + // check that all dispatchers exists in the `Interrupt` enumeration regardless of whether + // they are used or not + let interrupt = util::interrupt_ident(); + for name in app.args.dispatchers.keys() { + stmts.push(quote!(let _ = #rt_err::#interrupt::#name;)); + } + + let interrupt_ids = analysis.interrupts.iter().map(|(p, (id, _))| (p, id)); + + // Unmask interrupts and set their priorities + for (&priority, name) in interrupt_ids.chain(app.hardware_tasks.values().filter_map(|task| { + if util::is_exception(&task.args.binds) { + // We do exceptions in another pass + None + } else { + Some((&task.args.priority, &task.args.binds)) + } + })) { + let es = format!( + "Maximum priority used by interrupt vector '{name}' is more than supported by hardware" + ); + // Compile time assert that this priority is supported by the device + stmts.push(quote!( + const _: () = if (1 << #nvic_prio_bits) < #priority as usize { ::core::panic!(#es); }; + )); + + stmts.push(quote!( + core.NVIC.set_priority( + #rt_err::#interrupt::#name, + rtic::export::logical2hw(#priority, #nvic_prio_bits), + ); + )); + + // NOTE unmask the interrupt *after* setting its priority: changing the priority of a pended + // interrupt is implementation defined + stmts.push(quote!(rtic::export::NVIC::unmask(#rt_err::#interrupt::#name);)); + } + + // Set exception priorities + for (name, priority) in app.hardware_tasks.values().filter_map(|task| { + if util::is_exception(&task.args.binds) { + Some((&task.args.binds, task.args.priority)) + } else { + None + } + }) { + let es = format!( + "Maximum priority used by interrupt vector '{name}' is more than supported by hardware" + ); + // Compile time assert that this priority is supported by the device + stmts.push(quote!( + const _: () = if (1 << #nvic_prio_bits) < #priority as usize { ::core::panic!(#es); }; + )); + + stmts.push(quote!(core.SCB.set_priority( + rtic::export::SystemHandler::#name, + rtic::export::logical2hw(#priority, #nvic_prio_bits), + );)); + } + + stmts +} diff --git a/rtic-macros/src/codegen/shared_resources.rs b/rtic-macros/src/codegen/shared_resources.rs new file mode 100644 index 00000000..19fd13fe --- /dev/null +++ b/rtic-macros/src/codegen/shared_resources.rs @@ -0,0 +1,183 @@ +use crate::syntax::{analyze::Ownership, ast::App}; +use crate::{analyze::Analysis, codegen::util}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use std::collections::HashMap; + +/// Generates `static` variables and shared resource proxies +pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { + let mut mod_app = vec![]; + let mut mod_resources = vec![]; + + for (name, res) in &app.shared_resources { + let cfgs = &res.cfgs; + let ty = &res.ty; + let mangled_name = &util::static_shared_resource_ident(name); + + let attrs = &res.attrs; + + // late resources in `util::link_section_uninit` + // unless user specifies custom link section + let section = if attrs.iter().any(|attr| attr.path.is_ident("link_section")) { + None + } else { + Some(util::link_section_uninit()) + }; + + // For future use + // let doc = format!(" RTIC internal: {}:{}", file!(), line!()); + mod_app.push(quote!( + #[allow(non_camel_case_types)] + #[allow(non_upper_case_globals)] + // #[doc = #doc] + #[doc(hidden)] + #(#attrs)* + #(#cfgs)* + #section + static #mangled_name: rtic::RacyCell<core::mem::MaybeUninit<#ty>> = rtic::RacyCell::new(core::mem::MaybeUninit::uninit()); + )); + + // For future use + // let doc = format!(" RTIC internal: {}:{}", file!(), line!()); + + let shared_name = util::need_to_lock_ident(name); + + if !res.properties.lock_free { + mod_resources.push(quote!( + // #[doc = #doc] + #[doc(hidden)] + #[allow(non_camel_case_types)] + #(#cfgs)* + pub struct #shared_name<'a> { + __rtic_internal_p: ::core::marker::PhantomData<&'a ()>, + } + + #(#cfgs)* + impl<'a> #shared_name<'a> { + #[inline(always)] + pub unsafe fn new() -> Self { + #shared_name { __rtic_internal_p: ::core::marker::PhantomData } + } + } + )); + + let ptr = quote!( + #(#cfgs)* + #mangled_name.get_mut() as *mut _ + ); + + let ceiling = match analysis.ownerships.get(name) { + Some(Ownership::Owned { priority } | Ownership::CoOwned { priority }) => *priority, + Some(Ownership::Contended { ceiling }) => *ceiling, + None => 0, + }; + + // For future use + // let doc = format!(" RTIC internal ({} resource): {}:{}", doc, file!(), line!()); + + mod_app.push(util::impl_mutex( + app, + cfgs, + true, + &shared_name, + "e!(#ty), + ceiling, + &ptr, + )); + } + } + + let mod_resources = if mod_resources.is_empty() { + quote!() + } else { + quote!(mod shared_resources { + #(#mod_resources)* + }) + }; + + // Computing mapping of used interrupts to masks + let interrupt_ids = analysis.interrupts.iter().map(|(p, (id, _))| (p, id)); + + let mut prio_to_masks = HashMap::new(); + let device = &app.args.device; + let mut uses_exceptions_with_resources = false; + + let mut mask_ids = Vec::new(); + + for (&priority, name) in interrupt_ids.chain(app.hardware_tasks.values().flat_map(|task| { + if !util::is_exception(&task.args.binds) { + Some((&task.args.priority, &task.args.binds)) + } else { + // If any resource to the exception uses non-lock-free or non-local resources this is + // not allwed on thumbv6. + uses_exceptions_with_resources = uses_exceptions_with_resources + || task + .args + .shared_resources + .iter() + .map(|(ident, access)| { + if access.is_exclusive() { + if let Some(r) = app.shared_resources.get(ident) { + !r.properties.lock_free + } else { + false + } + } else { + false + } + }) + .any(|v| v); + + None + } + })) { + let v: &mut Vec<_> = prio_to_masks.entry(priority - 1).or_default(); + v.push(quote!(#device::Interrupt::#name as u32)); + mask_ids.push(quote!(#device::Interrupt::#name as u32)); + } + + // Call rtic::export::create_mask([Mask; N]), where the array is the list of shifts + + let mut mask_arr = Vec::new(); + // NOTE: 0..3 assumes max 4 priority levels according to M0, M23 spec + for i in 0..3 { + let v = if let Some(v) = prio_to_masks.get(&i) { + v.clone() + } else { + Vec::new() + }; + + mask_arr.push(quote!( + rtic::export::create_mask([#(#v),*]) + )); + } + + // Generate a constant for the number of chunks needed by Mask. + let chunks_name = util::priority_mask_chunks_ident(); + mod_app.push(quote!( + #[doc(hidden)] + #[allow(non_upper_case_globals)] + const #chunks_name: usize = rtic::export::compute_mask_chunks([#(#mask_ids),*]); + )); + + let masks_name = util::priority_masks_ident(); + mod_app.push(quote!( + #[doc(hidden)] + #[allow(non_upper_case_globals)] + const #masks_name: [rtic::export::Mask<#chunks_name>; 3] = [#(#mask_arr),*]; + )); + + if uses_exceptions_with_resources { + mod_app.push(quote!( + #[doc(hidden)] + #[allow(non_upper_case_globals)] + const __rtic_internal_V6_ERROR: () = rtic::export::no_basepri_panic(); + )); + } + + quote!( + #(#mod_app)* + + #mod_resources + ) +} diff --git a/rtic-macros/src/codegen/shared_resources_struct.rs b/rtic-macros/src/codegen/shared_resources_struct.rs new file mode 100644 index 00000000..fa6f0fcb --- /dev/null +++ b/rtic-macros/src/codegen/shared_resources_struct.rs @@ -0,0 +1,119 @@ +use crate::syntax::{ast::App, Context}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +use crate::codegen::util; + +/// Generate shared resources structs +pub fn codegen(ctxt: Context, app: &App) -> (TokenStream2, TokenStream2) { + let resources = match ctxt { + Context::Init => unreachable!("Tried to generate shared resources struct for init"), + Context::Idle => { + &app.idle + .as_ref() + .expect("RTIC-ICE: unable to get idle name") + .args + .shared_resources + } + Context::HardwareTask(name) => &app.hardware_tasks[name].args.shared_resources, + Context::SoftwareTask(name) => &app.software_tasks[name].args.shared_resources, + }; + + let mut fields = vec![]; + let mut values = vec![]; + + for (name, access) in resources { + let res = app.shared_resources.get(name).expect("UNREACHABLE"); + + let cfgs = &res.cfgs; + + // access hold if the resource is [x] (exclusive) or [&x] (shared) + let mut_ = if access.is_exclusive() { + Some(quote!(mut)) + } else { + None + }; + let ty = &res.ty; + let mangled_name = util::static_shared_resource_ident(name); + let shared_name = util::need_to_lock_ident(name); + + if res.properties.lock_free { + // Lock free resources of `idle` and `init` get 'static lifetime + let lt = if ctxt.runs_once() { + quote!('static) + } else { + quote!('a) + }; + + fields.push(quote!( + #(#cfgs)* + #[allow(missing_docs)] + pub #name: &#lt #mut_ #ty + )); + } else if access.is_shared() { + fields.push(quote!( + #(#cfgs)* + #[allow(missing_docs)] + pub #name: &'a #ty + )); + } else { + fields.push(quote!( + #(#cfgs)* + #[allow(missing_docs)] + pub #name: shared_resources::#shared_name<'a> + )); + + values.push(quote!( + #(#cfgs)* + #name: shared_resources::#shared_name::new() + + )); + + // continue as the value has been filled, + continue; + } + + let expr = if access.is_exclusive() { + quote!(&mut *(&mut *#mangled_name.get_mut()).as_mut_ptr()) + } else { + quote!(&*(&*#mangled_name.get()).as_ptr()) + }; + + values.push(quote!( + #(#cfgs)* + #name: #expr + )); + } + + fields.push(quote!( + #[doc(hidden)] + pub __rtic_internal_marker: core::marker::PhantomData<&'a ()> + )); + + values.push(quote!(__rtic_internal_marker: core::marker::PhantomData)); + + let doc = format!("Shared resources `{}` has access to", ctxt.ident(app)); + let ident = util::shared_resources_ident(ctxt, app); + let item = quote!( + #[allow(non_snake_case)] + #[allow(non_camel_case_types)] + #[doc = #doc] + pub struct #ident<'a> { + #(#fields,)* + } + ); + + let constructor = quote!( + impl<'a> #ident<'a> { + #[inline(always)] + #[allow(missing_docs)] + pub unsafe fn new() -> Self { + #ident { + #(#values,)* + } + } + } + ); + + (item, constructor) +} diff --git a/rtic-macros/src/codegen/software_tasks.rs b/rtic-macros/src/codegen/software_tasks.rs new file mode 100644 index 00000000..34fc851a --- /dev/null +++ b/rtic-macros/src/codegen/software_tasks.rs @@ -0,0 +1,64 @@ +use crate::syntax::{ast::App, Context}; +use crate::{ + analyze::Analysis, + codegen::{local_resources_struct, module, shared_resources_struct}, +}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { + let mut mod_app = vec![]; + let mut root = vec![]; + let mut user_tasks = vec![]; + + // Any task + for (name, task) in app.software_tasks.iter() { + if !task.args.local_resources.is_empty() { + let (item, constructor) = + local_resources_struct::codegen(Context::SoftwareTask(name), app); + + root.push(item); + + mod_app.push(constructor); + } + + if !task.args.shared_resources.is_empty() { + let (item, constructor) = + shared_resources_struct::codegen(Context::SoftwareTask(name), app); + + root.push(item); + + mod_app.push(constructor); + } + + if !&task.is_extern { + let context = &task.context; + let attrs = &task.attrs; + let cfgs = &task.cfgs; + let stmts = &task.stmts; + let inputs = &task.inputs; + + user_tasks.push(quote!( + #(#attrs)* + #(#cfgs)* + #[allow(non_snake_case)] + async fn #name<'a>(#context: #name::Context<'a> #(,#inputs)*) { + use rtic::Mutex as _; + use rtic::mutex::prelude::*; + + #(#stmts)* + } + )); + } + + root.push(module::codegen(Context::SoftwareTask(name), app, analysis)); + } + + quote!( + #(#mod_app)* + + #(#root)* + + #(#user_tasks)* + ) +} diff --git a/rtic-macros/src/codegen/util.rs b/rtic-macros/src/codegen/util.rs new file mode 100644 index 00000000..d0c8cc0e --- /dev/null +++ b/rtic-macros/src/codegen/util.rs @@ -0,0 +1,238 @@ +use crate::syntax::{ast::App, Context}; +use core::sync::atomic::{AtomicUsize, Ordering}; +use proc_macro2::{Span, TokenStream as TokenStream2}; +use quote::quote; +use syn::{Attribute, Ident, PatType}; + +const RTIC_INTERNAL: &str = "__rtic_internal"; + +/// Generates a `Mutex` implementation +pub fn impl_mutex( + app: &App, + cfgs: &[Attribute], + resources_prefix: bool, + name: &Ident, + ty: &TokenStream2, + ceiling: u8, + ptr: &TokenStream2, +) -> TokenStream2 { + let path = if resources_prefix { + quote!(shared_resources::#name) + } else { + quote!(#name) + }; + + let device = &app.args.device; + let masks_name = priority_masks_ident(); + quote!( + #(#cfgs)* + impl<'a> rtic::Mutex for #path<'a> { + type T = #ty; + + #[inline(always)] + fn lock<RTIC_INTERNAL_R>(&mut self, f: impl FnOnce(&mut #ty) -> RTIC_INTERNAL_R) -> RTIC_INTERNAL_R { + /// Priority ceiling + const CEILING: u8 = #ceiling; + + unsafe { + rtic::export::lock( + #ptr, + CEILING, + #device::NVIC_PRIO_BITS, + &#masks_name, + f, + ) + } + } + } + ) +} + +pub fn interrupt_ident() -> Ident { + let span = Span::call_site(); + Ident::new("interrupt", span) +} + +/// Whether `name` is an exception with configurable priority +pub fn is_exception(name: &Ident) -> bool { + let s = name.to_string(); + + matches!( + &*s, + "MemoryManagement" + | "BusFault" + | "UsageFault" + | "SecureFault" + | "SVCall" + | "DebugMonitor" + | "PendSV" + | "SysTick" + ) +} + +/// Mark a name as internal +pub fn mark_internal_name(name: &str) -> Ident { + Ident::new(&format!("{RTIC_INTERNAL}_{name}"), Span::call_site()) +} + +/// Generate an internal identifier for tasks +pub fn internal_task_ident(task: &Ident, ident_name: &str) -> Ident { + mark_internal_name(&format!("{task}_{ident_name}")) +} + +fn link_section_index() -> usize { + static INDEX: AtomicUsize = AtomicUsize::new(0); + + INDEX.fetch_add(1, Ordering::Relaxed) +} + +/// Add `link_section` attribute +pub fn link_section_uninit() -> TokenStream2 { + let section = format!(".uninit.rtic{}", link_section_index()); + + quote!(#[link_section = #section]) +} + +/// Regroups the inputs of a task +/// +/// `inputs` could be &[`input: Foo`] OR &[`mut x: i32`, `ref y: i64`] +pub fn regroup_inputs( + inputs: &[PatType], +) -> ( + // args e.g. &[`_0`], &[`_0: i32`, `_1: i64`] + Vec<TokenStream2>, + // tupled e.g. `_0`, `(_0, _1)` + TokenStream2, + // untupled e.g. &[`_0`], &[`_0`, `_1`] + Vec<TokenStream2>, + // ty e.g. `Foo`, `(i32, i64)` + TokenStream2, +) { + if inputs.len() == 1 { + let ty = &inputs[0].ty; + + ( + vec![quote!(_0: #ty)], + quote!(_0), + vec![quote!(_0)], + quote!(#ty), + ) + } else { + let mut args = vec![]; + let mut pats = vec![]; + let mut tys = vec![]; + + for (i, input) in inputs.iter().enumerate() { + let i = Ident::new(&format!("_{i}"), Span::call_site()); + let ty = &input.ty; + + args.push(quote!(#i: #ty)); + + pats.push(quote!(#i)); + + tys.push(quote!(#ty)); + } + + let tupled = { + let pats = pats.clone(); + quote!((#(#pats,)*)) + }; + let ty = quote!((#(#tys,)*)); + (args, tupled, pats, ty) + } +} + +/// Get the ident for the name of the task +pub fn get_task_name(ctxt: Context, app: &App) -> Ident { + let s = match ctxt { + Context::Init => app.init.name.to_string(), + Context::Idle => app + .idle + .as_ref() + .expect("RTIC-ICE: unable to find idle name") + .name + .to_string(), + Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(), + }; + + Ident::new(&s, Span::call_site()) +} + +/// Generates a pre-reexport identifier for the "shared resources" struct +pub fn shared_resources_ident(ctxt: Context, app: &App) -> Ident { + let mut s = match ctxt { + Context::Init => app.init.name.to_string(), + Context::Idle => app + .idle + .as_ref() + .expect("RTIC-ICE: unable to find idle name") + .name + .to_string(), + Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(), + }; + + s.push_str("SharedResources"); + + mark_internal_name(&s) +} + +/// Generates a pre-reexport identifier for the "local resources" struct +pub fn local_resources_ident(ctxt: Context, app: &App) -> Ident { + let mut s = match ctxt { + Context::Init => app.init.name.to_string(), + Context::Idle => app + .idle + .as_ref() + .expect("RTIC-ICE: unable to find idle name") + .name + .to_string(), + Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(), + }; + + s.push_str("LocalResources"); + + mark_internal_name(&s) +} + +/// Suffixed identifier +pub fn suffixed(name: &str) -> Ident { + let span = Span::call_site(); + Ident::new(name, span) +} + +pub fn static_shared_resource_ident(name: &Ident) -> Ident { + mark_internal_name(&format!("shared_resource_{name}")) +} + +/// Generates an Ident for the number of 32 bit chunks used for Mask storage. +pub fn priority_mask_chunks_ident() -> Ident { + mark_internal_name("MASK_CHUNKS") +} + +pub fn priority_masks_ident() -> Ident { + mark_internal_name("MASKS") +} + +pub fn static_local_resource_ident(name: &Ident) -> Ident { + mark_internal_name(&format!("local_resource_{name}")) +} + +pub fn declared_static_local_resource_ident(name: &Ident, task_name: &Ident) -> Ident { + mark_internal_name(&format!("local_{task_name}_{name}")) +} + +pub fn need_to_lock_ident(name: &Ident) -> Ident { + Ident::new(&format!("{name}_that_needs_to_be_locked"), name.span()) +} + +pub fn zero_prio_dispatcher_ident() -> Ident { + Ident::new("__rtic_internal_async_0_prio_dispatcher", Span::call_site()) +} + +/// The name to get better RT flag errors +pub fn rt_err_ident() -> Ident { + Ident::new( + "you_must_enable_the_rt_feature_for_the_pac_in_your_cargo_toml", + Span::call_site(), + ) +} |