diff options
author | 2023-01-23 20:05:47 +0100 | |
---|---|---|
committer | 2023-03-01 00:33:31 +0100 | |
commit | 306aa47170fd59369b7a184924e287dc3706d64d (patch) | |
tree | 75a331a63a4021f078e330bf2ce4edb1228e2ecf /macros/src | |
parent | b8b881f446a226d6f3c4a7db7c9174590b47dbf6 (diff) | |
download | rtic-306aa47170fd59369b7a184924e287dc3706d64d.tar.gz rtic-306aa47170fd59369b7a184924e287dc3706d64d.tar.zst rtic-306aa47170fd59369b7a184924e287dc3706d64d.zip |
Add rtic-timer (timerqueue + monotonic) and rtic-monotonics (systick-monotonic)
Diffstat (limited to 'macros/src')
42 files changed, 0 insertions, 4716 deletions
diff --git a/macros/src/analyze.rs b/macros/src/analyze.rs deleted file mode 100644 index 65774f6c..00000000 --- a/macros/src/analyze.rs +++ /dev/null @@ -1,49 +0,0 @@ -use core::ops; -use std::collections::{BTreeMap, BTreeSet}; - -use crate::syntax::{ - analyze::{self, Priority}, - ast::{App, Dispatcher}, -}; -use syn::Ident; - -/// Extend the upstream `Analysis` struct with our field -pub struct Analysis { - parent: analyze::Analysis, - pub interrupts: BTreeMap<Priority, (Ident, Dispatcher)>, -} - -impl ops::Deref for Analysis { - type Target = analyze::Analysis; - - fn deref(&self) -> &Self::Target { - &self.parent - } -} - -// Assign an interrupt to each priority level -pub fn app(analysis: analyze::Analysis, app: &App) -> Analysis { - let mut available_interrupt = app.args.dispatchers.clone(); - - // the set of priorities (each priority only once) - let priorities = app - .software_tasks - .values() - .map(|task| task.args.priority) - .collect::<BTreeSet<_>>(); - - // map from priorities to interrupts (holding name and attributes) - - let interrupts: BTreeMap<Priority, _> = priorities - .iter() - .filter(|prio| **prio > 0) // 0 prio tasks are run in main - .copied() - .rev() - .map(|p| (p, available_interrupt.pop().expect("UNREACHABLE"))) - .collect(); - - Analysis { - parent: analysis, - interrupts, - } -} diff --git a/macros/src/bindings.rs b/macros/src/bindings.rs deleted file mode 100644 index 8b137891..00000000 --- a/macros/src/bindings.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/macros/src/check.rs b/macros/src/check.rs deleted file mode 100644 index a05c82e8..00000000 --- a/macros/src/check.rs +++ /dev/null @@ -1,70 +0,0 @@ -use std::collections::HashSet; - -use crate::syntax::ast::App; -use syn::parse; - -pub fn app(app: &App) -> parse::Result<()> { - // Check that external (device-specific) interrupts are not named after known (Cortex-M) - // exceptions - for name in app.args.dispatchers.keys() { - let name_s = name.to_string(); - - match &*name_s { - "NonMaskableInt" | "HardFault" | "MemoryManagement" | "BusFault" | "UsageFault" - | "SecureFault" | "SVCall" | "DebugMonitor" | "PendSV" | "SysTick" => { - return Err(parse::Error::new( - name.span(), - "Cortex-M exceptions can't be used as `extern` interrupts", - )); - } - - _ => {} - } - } - - // Check that there are enough external interrupts to dispatch the software tasks and the timer - // queue handler - let mut first = None; - let priorities = app - .software_tasks - .iter() - .map(|(name, task)| { - first = Some(name); - task.args.priority - }) - .filter(|prio| *prio > 0) - .collect::<HashSet<_>>(); - - let need = priorities.len(); - let given = app.args.dispatchers.len(); - if need > given { - let s = { - format!( - "not enough interrupts to dispatch \ - all software tasks (need: {need}; given: {given})" - ) - }; - - // If not enough tasks and first still is None, may cause - // "custom attribute panicked" due to unwrap on None - return Err(parse::Error::new(first.unwrap().span(), s)); - } - - // Check that all exceptions are valid; only exceptions with configurable priorities are - // accepted - for (name, task) in &app.hardware_tasks { - let name_s = task.args.binds.to_string(); - match &*name_s { - "NonMaskableInt" | "HardFault" => { - return Err(parse::Error::new( - name.span(), - "only exceptions with configurable priority can be used as hardware tasks", - )); - } - - _ => {} - } - } - - Ok(()) -} diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs deleted file mode 100644 index 24e98ce9..00000000 --- a/macros/src/codegen.rs +++ /dev/null @@ -1,75 +0,0 @@ -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; - -use crate::analyze::Analysis; -use crate::syntax::ast::App; - -mod assertions; -mod async_dispatchers; -mod hardware_tasks; -mod idle; -mod init; -mod local_resources; -mod local_resources_struct; -mod module; -mod post_init; -mod pre_init; -mod shared_resources; -mod shared_resources_struct; -mod software_tasks; -mod util; - -mod main; - -// TODO: organize codegen to actual parts of code -// so `main::codegen` generates ALL the code for `fn main`, -// `software_tasks::codegen` generates ALL the code for software tasks etc... - -#[allow(clippy::too_many_lines)] -pub fn app(app: &App, analysis: &Analysis) -> TokenStream2 { - // Generate the `main` function - let main = main::codegen(app, analysis); - let init_codegen = init::codegen(app, analysis); - let idle_codegen = idle::codegen(app, analysis); - let shared_resources_codegen = shared_resources::codegen(app, analysis); - let local_resources_codegen = local_resources::codegen(app, analysis); - let hardware_tasks_codegen = hardware_tasks::codegen(app, analysis); - let software_tasks_codegen = software_tasks::codegen(app, analysis); - let async_dispatchers_codegen = async_dispatchers::codegen(app, analysis); - - let user_imports = &app.user_imports; - let user_code = &app.user_code; - let name = &app.name; - let device = &app.args.device; - - let rt_err = util::rt_err_ident(); - - quote!( - /// The RTIC application module - pub mod #name { - /// Always include the device crate which contains the vector table - use #device as #rt_err; - - #(#user_imports)* - - #(#user_code)* - /// User code end - - #init_codegen - - #idle_codegen - - #hardware_tasks_codegen - - #software_tasks_codegen - - #shared_resources_codegen - - #local_resources_codegen - - #async_dispatchers_codegen - - #main - } - ) -} diff --git a/macros/src/codegen/assertions.rs b/macros/src/codegen/assertions.rs deleted file mode 100644 index dd94aa6d..00000000 --- a/macros/src/codegen/assertions.rs +++ /dev/null @@ -1,53 +0,0 @@ -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/macros/src/codegen/async_dispatchers.rs b/macros/src/codegen/async_dispatchers.rs deleted file mode 100644 index a12ad325..00000000 --- a/macros/src/codegen/async_dispatchers.rs +++ /dev/null @@ -1,89 +0,0 @@ -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/macros/src/codegen/hardware_tasks.rs b/macros/src/codegen/hardware_tasks.rs deleted file mode 100644 index 8a5a8f6c..00000000 --- a/macros/src/codegen/hardware_tasks.rs +++ /dev/null @@ -1,87 +0,0 @@ -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/macros/src/codegen/idle.rs b/macros/src/codegen/idle.rs deleted file mode 100644 index 0c833ef3..00000000 --- a/macros/src/codegen/idle.rs +++ /dev/null @@ -1,58 +0,0 @@ -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/macros/src/codegen/init.rs b/macros/src/codegen/init.rs deleted file mode 100644 index 6e1059f7..00000000 --- a/macros/src/codegen/init.rs +++ /dev/null @@ -1,95 +0,0 @@ -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/macros/src/codegen/local_resources.rs b/macros/src/codegen/local_resources.rs deleted file mode 100644 index e6d15533..00000000 --- a/macros/src/codegen/local_resources.rs +++ /dev/null @@ -1,65 +0,0 @@ -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/macros/src/codegen/local_resources_struct.rs b/macros/src/codegen/local_resources_struct.rs deleted file mode 100644 index 100c3eb5..00000000 --- a/macros/src/codegen/local_resources_struct.rs +++ /dev/null @@ -1,102 +0,0 @@ -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/macros/src/codegen/main.rs b/macros/src/codegen/main.rs deleted file mode 100644 index 2775d259..00000000 --- a/macros/src/codegen/main.rs +++ /dev/null @@ -1,52 +0,0 @@ -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/macros/src/codegen/module.rs b/macros/src/codegen/module.rs deleted file mode 100644 index 4725b9a9..00000000 --- a/macros/src/codegen/module.rs +++ /dev/null @@ -1,194 +0,0 @@ -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> { - - if #exec_name.spawn(|| #name(unsafe { #name::Context::new() } #(,#input_untupled)*) ) { - - #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/macros/src/codegen/post_init.rs b/macros/src/codegen/post_init.rs deleted file mode 100644 index c4e53837..00000000 --- a/macros/src/codegen/post_init.rs +++ /dev/null @@ -1,47 +0,0 @@ -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/macros/src/codegen/pre_init.rs b/macros/src/codegen/pre_init.rs deleted file mode 100644 index 28ba29c0..00000000 --- a/macros/src/codegen/pre_init.rs +++ /dev/null @@ -1,85 +0,0 @@ -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/macros/src/codegen/shared_resources.rs b/macros/src/codegen/shared_resources.rs deleted file mode 100644 index 19fd13fe..00000000 --- a/macros/src/codegen/shared_resources.rs +++ /dev/null @@ -1,183 +0,0 @@ -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/macros/src/codegen/shared_resources_struct.rs b/macros/src/codegen/shared_resources_struct.rs deleted file mode 100644 index fa6f0fcb..00000000 --- a/macros/src/codegen/shared_resources_struct.rs +++ /dev/null @@ -1,119 +0,0 @@ -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/macros/src/codegen/software_tasks.rs b/macros/src/codegen/software_tasks.rs deleted file mode 100644 index 34fc851a..00000000 --- a/macros/src/codegen/software_tasks.rs +++ /dev/null @@ -1,64 +0,0 @@ -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/macros/src/codegen/util.rs b/macros/src/codegen/util.rs deleted file mode 100644 index e121487c..00000000 --- a/macros/src/codegen/util.rs +++ /dev/null @@ -1,238 +0,0 @@ -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(), - ) -} diff --git a/macros/src/lib.rs b/macros/src/lib.rs deleted file mode 100644 index a8422d09..00000000 --- a/macros/src/lib.rs +++ /dev/null @@ -1,92 +0,0 @@ -#![doc( - html_logo_url = "https://raw.githubusercontent.com/rtic-rs/rtic/master/book/en/src/RTIC.svg", - html_favicon_url = "https://raw.githubusercontent.com/rtic-rs/rtic/master/book/en/src/RTIC.svg" -)] - -//deny_warnings_placeholder_for_ci - -use proc_macro::TokenStream; -use std::{env, fs, path::Path}; - -mod analyze; -mod bindings; -mod check; -mod codegen; -mod syntax; - -// Used for mocking the API in testing -#[doc(hidden)] -#[proc_macro_attribute] -pub fn mock_app(args: TokenStream, input: TokenStream) -> TokenStream { - if let Err(e) = syntax::parse(args, input) { - e.to_compile_error().into() - } else { - "fn main() {}".parse().unwrap() - } -} - -/// Attribute used to declare a RTIC application -/// -/// For user documentation see the [RTIC book](https://rtic.rs) -/// -/// # Panics -/// -/// Should never panic, cargo feeds a path which is later converted to a string -#[proc_macro_attribute] -pub fn app(args: TokenStream, input: TokenStream) -> TokenStream { - let (app, analysis) = match syntax::parse(args, input) { - Err(e) => return e.to_compile_error().into(), - Ok(x) => x, - }; - - if let Err(e) = check::app(&app) { - return e.to_compile_error().into(); - } - - let analysis = analyze::app(analysis, &app); - - let ts = codegen::app(&app, &analysis); - - // Default output path: <project_dir>/target/ - let mut out_dir = Path::new("target"); - - // Get output directory from Cargo environment - // TODO don't want to break builds if OUT_DIR is not set, is this ever the case? - let out_str = env::var("OUT_DIR").unwrap_or_else(|_| "".to_string()); - - if !out_dir.exists() { - // Set out_dir to OUT_DIR - out_dir = Path::new(&out_str); - - // Default build path, annotated below: - // $(pwd)/target/thumbv7em-none-eabihf/debug/build/cortex-m-rtic-<HASH>/out/ - // <project_dir>/<target-dir>/<TARGET>/debug/build/cortex-m-rtic-<HASH>/out/ - // - // traverse up to first occurrence of TARGET, approximated with starts_with("thumbv") - // and use the parent() of this path - // - // If no "target" directory is found, <project_dir>/<out_dir_root> is used - for path in out_dir.ancestors() { - if let Some(dir) = path.components().last() { - let dir = dir.as_os_str().to_str().unwrap(); - - if dir.starts_with("thumbv") || dir.starts_with("riscv") { - if let Some(out) = path.parent() { - out_dir = out; - break; - } - // If no parent, just use it - out_dir = path; - break; - } - } - } - } - - // Try to write the expanded code to disk - if let Some(out_str) = out_dir.to_str() { - fs::write(format!("{out_str}/rtic-expansion.rs"), ts.to_string()).ok(); - } - - ts.into() -} diff --git a/macros/src/syntax.rs b/macros/src/syntax.rs deleted file mode 100644 index d6f5a476..00000000 --- a/macros/src/syntax.rs +++ /dev/null @@ -1,121 +0,0 @@ -#[allow(unused_extern_crates)] -extern crate proc_macro; - -use proc_macro::TokenStream; - -use indexmap::{IndexMap, IndexSet}; -use proc_macro2::TokenStream as TokenStream2; -use syn::Ident; - -use crate::syntax::ast::App; - -mod accessors; -pub mod analyze; -pub mod ast; -mod check; -mod parse; - -/// An ordered map keyed by identifier -pub type Map<T> = IndexMap<Ident, T>; - -/// An order set -pub type Set<T> = IndexSet<T>; - -/// Execution context -#[derive(Clone, Copy)] -pub enum Context<'a> { - /// The `idle` context - Idle, - - /// The `init`-ialization function - Init, - - /// A async software task - SoftwareTask(&'a Ident), - - /// A hardware task - HardwareTask(&'a Ident), -} - -impl<'a> Context<'a> { - /// The identifier of this context - pub fn ident(&self, app: &'a App) -> &'a Ident { - match self { - Context::HardwareTask(ident) => ident, - Context::Idle => &app.idle.as_ref().unwrap().name, - Context::Init => &app.init.name, - Context::SoftwareTask(ident) => ident, - } - } - - /// Is this the `idle` context? - pub fn is_idle(&self) -> bool { - matches!(self, Context::Idle) - } - - /// Is this the `init`-ialization context? - pub fn is_init(&self) -> bool { - matches!(self, Context::Init) - } - - /// Whether this context runs only once - pub fn runs_once(&self) -> bool { - self.is_init() || self.is_idle() - } - - /// Whether this context has shared resources - pub fn has_shared_resources(&self, app: &App) -> bool { - match *self { - Context::HardwareTask(name) => { - !app.hardware_tasks[name].args.shared_resources.is_empty() - } - Context::Idle => !app.idle.as_ref().unwrap().args.shared_resources.is_empty(), - Context::Init => false, - Context::SoftwareTask(name) => { - !app.software_tasks[name].args.shared_resources.is_empty() - } - } - } - - /// Whether this context has local resources - pub fn has_local_resources(&self, app: &App) -> bool { - match *self { - Context::HardwareTask(name) => { - !app.hardware_tasks[name].args.local_resources.is_empty() - } - Context::Idle => !app.idle.as_ref().unwrap().args.local_resources.is_empty(), - Context::Init => !app.init.args.local_resources.is_empty(), - Context::SoftwareTask(name) => { - !app.software_tasks[name].args.local_resources.is_empty() - } - } - } -} - -/// Parses the input of the `#[app]` attribute -pub fn parse( - args: TokenStream, - input: TokenStream, -) -> Result<(ast::App, analyze::Analysis), syn::parse::Error> { - parse2(args.into(), input.into()) -} - -/// `proc_macro2::TokenStream` version of `parse` -pub fn parse2( - args: TokenStream2, - input: TokenStream2, -) -> Result<(ast::App, analyze::Analysis), syn::parse::Error> { - let app = parse::app(args, input)?; - check::app(&app)?; - - match analyze::app(&app) { - Err(e) => Err(e), - // If no errors, return the app and analysis results - Ok(analysis) => Ok((app, analysis)), - } -} - -enum Either<A, B> { - Left(A), - Right(B), -} diff --git a/macros/src/syntax/.github/bors.toml b/macros/src/syntax/.github/bors.toml deleted file mode 100644 index aee6042f..00000000 --- a/macros/src/syntax/.github/bors.toml +++ /dev/null @@ -1,3 +0,0 @@ -block_labels = ["S-blocked"] -delete_merged_branches = true -status = ["ci"] diff --git a/macros/src/syntax/.github/workflows/build.yml b/macros/src/syntax/.github/workflows/build.yml deleted file mode 100644 index 29971b10..00000000 --- a/macros/src/syntax/.github/workflows/build.yml +++ /dev/null @@ -1,213 +0,0 @@ -name: Build -on: - pull_request: - push: - branches: - - master - - staging - - trying - - bors/staging - - bors/trying - -env: - CARGO_TERM_COLOR: always - -jobs: - # Run cargo fmt --check - style: - name: style - runs-on: ubuntu-20.04 - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - components: rustfmt - - - name: Fail on warnings - run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs - - - name: cargo fmt --check - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check - - # Compilation check - check: - name: check - runs-on: ubuntu-20.04 - strategy: - matrix: - target: - - x86_64-unknown-linux-gnu - toolchain: - - stable - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Install Rust ${{ matrix.toolchain }} with target (${{ matrix.target }}) - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.toolchain }} - target: ${{ matrix.target }} - override: true - - - name: Fail on warnings - run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs - - - name: Cache Dependencies - uses: Swatinem/rust-cache@v1 - - - name: cargo check - uses: actions-rs/cargo@v1 - with: - use-cross: false - command: check - args: --target=${{ matrix.target }} - - # Clippy - clippy: - name: Cargo clippy - runs-on: ubuntu-20.04 - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Install Rust stable - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: x86_64-unknown-linux-gnu - override: true - - - name: Fail on warnings - run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs - - - name: Cache Dependencies - uses: Swatinem/rust-cache@v1 - - - name: cargo clippy - uses: actions-rs/cargo@v1 - with: - use-cross: false - command: clippy - - # Verify all examples - testexamples: - name: testexamples - runs-on: ubuntu-20.04 - strategy: - matrix: - target: - - x86_64-unknown-linux-gnu - toolchain: - - stable - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Install Rust ${{ matrix.toolchain }} with target (${{ matrix.target }}) - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.toolchain }} - target: ${{ matrix.target }} - override: true - - - name: Cache Dependencies - uses: Swatinem/rust-cache@v1 - - - name: Fail on warnings - run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs - - - uses: actions-rs/cargo@v1 - with: - use-cross: false - command: test - args: --examples - - # Run test suite for UI - testui: - name: testui - runs-on: ubuntu-20.04 - strategy: - matrix: - target: - - x86_64-unknown-linux-gnu - toolchain: - - stable - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Install Rust ${{ matrix.toolchain }} with target (${{ matrix.target }}) - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.toolchain }} - target: ${{ matrix.target }} - override: true - - - name: Cache Dependencies - uses: Swatinem/rust-cache@v1 - - - name: Fail on warnings - run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs - - - - uses: actions-rs/cargo@v1 - with: - use-cross: false - command: test - args: --test ui - - # Run test suite - test: - name: test - runs-on: ubuntu-20.04 - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: thumbv7m-none-eabi - override: true - - - name: Cache Dependencies - uses: Swatinem/rust-cache@v1 - - - name: Fail on warnings - run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs - - - uses: actions-rs/cargo@v1 - with: - use-cross: false - command: test - args: --lib - - # Refs: https://github.com/rust-lang/crater/blob/9ab6f9697c901c4a44025cf0a39b73ad5b37d198/.github/workflows/bors.yml#L125-L149 - # - # ALL THE PREVIOUS JOBS NEEDS TO BE ADDED TO THE `needs` SECTION OF THIS JOB! - - ci-success: - name: ci - if: github.event_name == 'push' && success() - needs: - - style - - check - - clippy - - testexamples - - test - - testui - runs-on: ubuntu-20.04 - steps: - - name: Mark the job as a success - run: exit 0 diff --git a/macros/src/syntax/.github/workflows/changelog.yml b/macros/src/syntax/.github/workflows/changelog.yml deleted file mode 100644 index ccf6eb91..00000000 --- a/macros/src/syntax/.github/workflows/changelog.yml +++ /dev/null @@ -1,28 +0,0 @@ -# Check that the changelog is updated for all changes. -# -# This is only run for PRs. - -on: - pull_request: - # opened, reopened, synchronize are the default types for pull_request. - # labeled, unlabeled ensure this check is also run if a label is added or removed. - types: [opened, reopened, labeled, unlabeled, synchronize] - -name: Changelog - -jobs: - changelog: - name: Changelog - runs-on: ubuntu-latest - steps: - - name: Checkout sources - uses: actions/checkout@v2 - - - name: Check that changelog updated - uses: dangoslen/changelog-enforcer@v3 - with: - changeLogPath: CHANGELOG.md - skipLabels: 'needs-changelog, skip-changelog' - missingUpdateErrorMessage: 'Please add a changelog entry in the CHANGELOG.md file.' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file diff --git a/macros/src/syntax/.github/workflows/properties/build.properties.json b/macros/src/syntax/.github/workflows/properties/build.properties.json deleted file mode 100644 index fd3eed37..00000000 --- a/macros/src/syntax/.github/workflows/properties/build.properties.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Build", - "description": "RTIC Test Suite", - "iconName": "rust", - "categories": ["Rust"] -} diff --git a/macros/src/syntax/.gitignore b/macros/src/syntax/.gitignore deleted file mode 100644 index f8d7c8b4..00000000 --- a/macros/src/syntax/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -**/*.rs.bk -.#* -/target/ -Cargo.lock diff --git a/macros/src/syntax/.travis.yml b/macros/src/syntax/.travis.yml deleted file mode 100644 index 52d1ffdd..00000000 --- a/macros/src/syntax/.travis.yml +++ /dev/null @@ -1,31 +0,0 @@ -language: rust - -matrix: - include: - # MSRV - - env: TARGET=x86_64-unknown-linux-gnu - rust: 1.36.0 - - - env: TARGET=x86_64-unknown-linux-gnu - rust: stable - -before_install: set -e - -script: - - bash ci/script.sh - -after_script: set +e - -cache: cargo - -before_cache: - - chmod -R a+r $HOME/.cargo; - -branches: - only: - - staging - - trying - -notifications: - email: - on_success: never diff --git a/macros/src/syntax/accessors.rs b/macros/src/syntax/accessors.rs deleted file mode 100644 index e75dde6c..00000000 --- a/macros/src/syntax/accessors.rs +++ /dev/null @@ -1,113 +0,0 @@ -use syn::Ident; - -use crate::syntax::{ - analyze::Priority, - ast::{Access, App, Local, TaskLocal}, -}; - -impl App { - pub(crate) fn shared_resource_accesses( - &self, - ) -> impl Iterator<Item = (Option<Priority>, &Ident, Access)> { - self.idle - .iter() - .flat_map(|idle| { - idle.args - .shared_resources - .iter() - .map(move |(name, access)| (Some(0), name, *access)) - }) - .chain(self.hardware_tasks.values().flat_map(|task| { - task.args - .shared_resources - .iter() - .map(move |(name, access)| (Some(task.args.priority), name, *access)) - })) - .chain(self.software_tasks.values().flat_map(|task| { - task.args - .shared_resources - .iter() - .map(move |(name, access)| (Some(task.args.priority), name, *access)) - })) - } - - fn is_external(task_local: &TaskLocal) -> bool { - matches!(task_local, TaskLocal::External) - } - - pub(crate) fn local_resource_accesses(&self) -> impl Iterator<Item = &Ident> { - self.init - .args - .local_resources - .iter() - .filter(|(_, task_local)| Self::is_external(task_local)) // Only check the resources declared in `#[local]` - .map(move |(name, _)| name) - .chain(self.idle.iter().flat_map(|idle| { - idle.args - .local_resources - .iter() - .filter(|(_, task_local)| Self::is_external(task_local)) // Only check the resources declared in `#[local]` - .map(move |(name, _)| name) - })) - .chain(self.hardware_tasks.values().flat_map(|task| { - task.args - .local_resources - .iter() - .filter(|(_, task_local)| Self::is_external(task_local)) // Only check the resources declared in `#[local]` - .map(move |(name, _)| name) - })) - .chain(self.software_tasks.values().flat_map(|task| { - task.args - .local_resources - .iter() - .filter(|(_, task_local)| Self::is_external(task_local)) // Only check the resources declared in `#[local]` - .map(move |(name, _)| name) - })) - } - - fn get_declared_local(tl: &TaskLocal) -> Option<&Local> { - match tl { - TaskLocal::External => None, - TaskLocal::Declared(l) => Some(l), - } - } - - /// Get all declared local resources, i.e. `local = [NAME: TYPE = EXPR]`. - /// - /// Returns a vector of (task name, resource name, `Local` struct) - pub fn declared_local_resources(&self) -> Vec<(&Ident, &Ident, &Local)> { - self.init - .args - .local_resources - .iter() - .filter_map(move |(name, tl)| { - Self::get_declared_local(tl).map(|l| (&self.init.name, name, l)) - }) - .chain(self.idle.iter().flat_map(|idle| { - idle.args - .local_resources - .iter() - .filter_map(move |(name, tl)| { - Self::get_declared_local(tl) - .map(|l| (&self.idle.as_ref().unwrap().name, name, l)) - }) - })) - .chain(self.hardware_tasks.iter().flat_map(|(task_name, task)| { - task.args - .local_resources - .iter() - .filter_map(move |(name, tl)| { - Self::get_declared_local(tl).map(|l| (task_name, name, l)) - }) - })) - .chain(self.software_tasks.iter().flat_map(|(task_name, task)| { - task.args - .local_resources - .iter() - .filter_map(move |(name, tl)| { - Self::get_declared_local(tl).map(|l| (task_name, name, l)) - }) - })) - .collect() - } -} diff --git a/macros/src/syntax/analyze.rs b/macros/src/syntax/analyze.rs deleted file mode 100644 index 3ed14877..00000000 --- a/macros/src/syntax/analyze.rs +++ /dev/null @@ -1,417 +0,0 @@ -//! RTIC application analysis - -use core::cmp; -use std::collections::{BTreeMap, BTreeSet, HashMap}; - -use indexmap::{IndexMap, IndexSet}; -use syn::{Ident, Type}; - -use crate::syntax::{ - ast::{App, LocalResources, TaskLocal}, - Set, -}; - -pub(crate) fn app(app: &App) -> Result<Analysis, syn::Error> { - // Collect all tasks into a vector - type TaskName = Ident; - type Priority = u8; - - // The task list is a Tuple (Name, Shared Resources, Local Resources, Priority) - let task_resources_list: Vec<(TaskName, Vec<&Ident>, &LocalResources, Priority)> = - Some(&app.init) - .iter() - .map(|ht| (ht.name.clone(), Vec::new(), &ht.args.local_resources, 0)) - .chain(app.idle.iter().map(|ht| { - ( - ht.name.clone(), - ht.args - .shared_resources - .iter() - .map(|(v, _)| v) - .collect::<Vec<_>>(), - &ht.args.local_resources, - 0, - ) - })) - .chain(app.software_tasks.iter().map(|(name, ht)| { - ( - name.clone(), - ht.args - .shared_resources - .iter() - .map(|(v, _)| v) - .collect::<Vec<_>>(), - &ht.args.local_resources, - ht.args.priority, - ) - })) - .chain(app.hardware_tasks.iter().map(|(name, ht)| { - ( - name.clone(), - ht.args - .shared_resources - .iter() - .map(|(v, _)| v) - .collect::<Vec<_>>(), - &ht.args.local_resources, - ht.args.priority, - ) - })) - .collect(); - - let mut error = vec![]; - let mut lf_res_with_error = vec![]; - let mut lf_hash = HashMap::new(); - - // Collect lock free resources - let lock_free: Vec<&Ident> = app - .shared_resources - .iter() - .filter(|(_, r)| r.properties.lock_free) - .map(|(i, _)| i) - .collect(); - - // Check that lock_free resources are correct - for lf_res in lock_free.iter() { - for (task, tr, _, priority) in task_resources_list.iter() { - for r in tr { - // Get all uses of resources annotated lock_free - if lf_res == r { - // Check so async tasks do not use lock free resources - if app.software_tasks.get(task).is_some() { - error.push(syn::Error::new( - r.span(), - format!( - "Lock free shared resource {:?} is used by an async tasks, which is forbidden", - r.to_string(), - ), - )); - } - - // HashMap returns the previous existing object if old.key == new.key - if let Some(lf_res) = lf_hash.insert(r.to_string(), (task, r, priority)) { - // Check if priority differ, if it does, append to - // list of resources which will be annotated with errors - if priority != lf_res.2 { - lf_res_with_error.push(lf_res.1); - lf_res_with_error.push(r); - } - - // If the resource already violates lock free properties - if lf_res_with_error.contains(&r) { - lf_res_with_error.push(lf_res.1); - lf_res_with_error.push(r); - } - } - } - } - } - } - - // Add error message in the resource struct - for r in lock_free { - if lf_res_with_error.contains(&&r) { - error.push(syn::Error::new( - r.span(), - format!( - "Lock free shared resource {:?} is used by tasks at different priorities", - r.to_string(), - ), - )); - } - } - - // Add error message for each use of the shared resource - for resource in lf_res_with_error.clone() { - error.push(syn::Error::new( - resource.span(), - format!( - "Shared resource {:?} is declared lock free but used by tasks at different priorities", - resource.to_string(), - ), - )); - } - - // Collect local resources - let local: Vec<&Ident> = app.local_resources.iter().map(|(i, _)| i).collect(); - - let mut lr_with_error = vec![]; - let mut lr_hash = HashMap::new(); - - // Check that local resources are not shared - for lr in local { - for (task, _, local_resources, _) in task_resources_list.iter() { - for (name, res) in local_resources.iter() { - // Get all uses of resources annotated lock_free - if lr == name { - match res { - TaskLocal::External => { - // HashMap returns the previous existing object if old.key == new.key - if let Some(lr) = lr_hash.insert(name.to_string(), (task, name)) { - lr_with_error.push(lr.1); - lr_with_error.push(name); - } - } - // If a declared local has the same name as the `#[local]` struct, it's an - // direct error - TaskLocal::Declared(_) => { - lr_with_error.push(lr); - lr_with_error.push(name); - } - } - } - } - } - } - - // Add error message for each use of the local resource - for resource in lr_with_error.clone() { - error.push(syn::Error::new( - resource.span(), - format!( - "Local resource {:?} is used by multiple tasks or collides with multiple definitions", - resource.to_string(), - ), - )); - } - - // Check 0-priority async software tasks and idle dependency - for (name, task) in &app.software_tasks { - if task.args.priority == 0 { - // If there is a 0-priority task, there must be no idle - if app.idle.is_some() { - error.push(syn::Error::new( - name.span(), - format!( - "Async task {:?} has priority 0, but `#[idle]` is defined. 0-priority async tasks are only allowed if there is no `#[idle]`.", - name.to_string(), - ) - )); - } - } - } - - // Collect errors if any and return/halt - if !error.is_empty() { - let mut err = error.get(0).unwrap().clone(); - error.iter().for_each(|e| err.combine(e.clone())); - return Err(err); - } - - // e. Location of resources - let mut used_shared_resource = IndexSet::new(); - let mut ownerships = Ownerships::new(); - let mut sync_types = SyncTypes::new(); - for (prio, name, access) in app.shared_resource_accesses() { - let res = app.shared_resources.get(name).expect("UNREACHABLE"); - - // (e) - // This shared resource is used - used_shared_resource.insert(name.clone()); - - // (c) - if let Some(priority) = prio { - if let Some(ownership) = ownerships.get_mut(name) { - match *ownership { - Ownership::Owned { priority: ceiling } - | Ownership::CoOwned { priority: ceiling } - | Ownership::Contended { ceiling } - if priority != ceiling => - { - *ownership = Ownership::Contended { - ceiling: cmp::max(ceiling, priority), - }; - - if access.is_shared() { - sync_types.insert(res.ty.clone()); - } - } - - Ownership::Owned { priority: ceil } if ceil == priority => { - *ownership = Ownership::CoOwned { priority }; - } - - _ => {} - } - } else { - ownerships.insert(name.clone(), Ownership::Owned { priority }); - } - } - } - - // Create the list of used local resource Idents - let mut used_local_resource = IndexSet::new(); - - for (_, _, locals, _) in task_resources_list { - for (local, _) in locals { - used_local_resource.insert(local.clone()); - } - } - - // Most shared resources need to be `Send`, only 0 prio does not need it - let mut send_types = SendTypes::new(); - - for (name, res) in app.shared_resources.iter() { - if ownerships - .get(name) - .map(|ownership| match *ownership { - Ownership::Owned { priority: ceiling } - | Ownership::CoOwned { priority: ceiling } - | Ownership::Contended { ceiling } => ceiling != 0, - }) - .unwrap_or(false) - { - send_types.insert(res.ty.clone()); - } - } - - // Most local resources need to be `Send` as well, only 0 prio does not need it - for (name, res) in app.local_resources.iter() { - if ownerships - .get(name) - .map(|ownership| match *ownership { - Ownership::Owned { priority: ceiling } - | Ownership::CoOwned { priority: ceiling } - | Ownership::Contended { ceiling } => ceiling != 0, - }) - .unwrap_or(false) - { - send_types.insert(res.ty.clone()); - } - } - - let mut channels = Channels::new(); - - for (name, spawnee) in &app.software_tasks { - let spawnee_prio = spawnee.args.priority; - - let channel = channels.entry(spawnee_prio).or_default(); - channel.tasks.insert(name.clone()); - - // All inputs are send as we do not know from where they may be spawned. - spawnee.inputs.iter().for_each(|input| { - send_types.insert(input.ty.clone()); - }); - } - - // No channel should ever be empty - debug_assert!(channels.values().all(|channel| !channel.tasks.is_empty())); - - Ok(Analysis { - channels, - shared_resources: used_shared_resource, - local_resources: used_local_resource, - ownerships, - send_types, - sync_types, - }) -} - -// /// Priority ceiling -// pub type Ceiling = Option<u8>; - -/// Task priority -pub type Priority = u8; - -/// Resource name -pub type Resource = Ident; - -/// Task name -pub type Task = Ident; - -/// The result of analyzing an RTIC application -pub struct Analysis { - /// SPSC message channels - pub channels: Channels, - - /// Shared resources - /// - /// If a resource is not listed here it means that's a "dead" (never - /// accessed) resource and the backend should not generate code for it - pub shared_resources: UsedSharedResource, - - /// Local resources - /// - /// If a resource is not listed here it means that's a "dead" (never - /// accessed) resource and the backend should not generate code for it - pub local_resources: UsedLocalResource, - - /// Resource ownership - pub ownerships: Ownerships, - - /// These types must implement the `Send` trait - pub send_types: SendTypes, - - /// These types must implement the `Sync` trait - pub sync_types: SyncTypes, -} - -/// All channels, keyed by dispatch priority -pub type Channels = BTreeMap<Priority, Channel>; - -/// Location of all *used* shared resources -pub type UsedSharedResource = IndexSet<Resource>; - -/// Location of all *used* local resources -pub type UsedLocalResource = IndexSet<Resource>; - -/// Resource ownership -pub type Ownerships = IndexMap<Resource, Ownership>; - -/// These types must implement the `Send` trait -pub type SendTypes = Set<Box<Type>>; - -/// These types must implement the `Sync` trait -pub type SyncTypes = Set<Box<Type>>; - -/// A channel used to send messages -#[derive(Debug, Default)] -pub struct Channel { - /// The channel capacity - pub capacity: u8, - - /// Tasks that can be spawned on this channel - pub tasks: BTreeSet<Task>, -} - -/// Resource ownership -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum Ownership { - /// Owned by a single task - Owned { - /// Priority of the task that owns this resource - priority: u8, - }, - - /// "Co-owned" by more than one task; all of them have the same priority - CoOwned { - /// Priority of the tasks that co-own this resource - priority: u8, - }, - - /// Contended by more than one task; the tasks have different priorities - Contended { - /// Priority ceiling - ceiling: u8, - }, -} - -// impl Ownership { -// /// Whether this resource needs to a lock at this priority level -// pub fn needs_lock(&self, priority: u8) -> bool { -// match self { -// Ownership::Owned { .. } | Ownership::CoOwned { .. } => false, -// -// Ownership::Contended { ceiling } => { -// debug_assert!(*ceiling >= priority); -// -// priority < *ceiling -// } -// } -// } -// -// /// Whether this resource is exclusively owned -// pub fn is_owned(&self) -> bool { -// matches!(self, Ownership::Owned { .. }) -// } -// } diff --git a/macros/src/syntax/ast.rs b/macros/src/syntax/ast.rs deleted file mode 100644 index 27e6773f..00000000 --- a/macros/src/syntax/ast.rs +++ /dev/null @@ -1,335 +0,0 @@ -//! Abstract Syntax Tree - -use syn::{Attribute, Expr, Ident, Item, ItemUse, Pat, PatType, Path, Stmt, Type}; - -use crate::syntax::Map; - -/// The `#[app]` attribute -#[derive(Debug)] -#[non_exhaustive] -pub struct App { - /// The arguments to the `#[app]` attribute - pub args: AppArgs, - - /// The name of the `const` item on which the `#[app]` attribute has been placed - pub name: Ident, - - /// The `#[init]` function - pub init: Init, - - /// The `#[idle]` function - pub idle: Option<Idle>, - - /// Resources shared between tasks defined in `#[shared]` - pub shared_resources: Map<SharedResource>, - - /// Task local resources defined in `#[local]` - pub local_resources: Map<LocalResource>, - - /// User imports - pub user_imports: Vec<ItemUse>, - - /// User code - pub user_code: Vec<Item>, - - /// Hardware tasks: `#[task(binds = ..)]`s - pub hardware_tasks: Map<HardwareTask>, - - /// Async software tasks: `#[task]` - pub software_tasks: Map<SoftwareTask>, -} - -/// Interrupts used to dispatch software tasks -pub type Dispatchers = Map<Dispatcher>; - -/// Interrupt that could be used to dispatch software tasks -#[derive(Debug, Clone)] -#[non_exhaustive] -pub struct Dispatcher { - /// Attributes that will apply to this interrupt handler - pub attrs: Vec<Attribute>, -} - -/// The arguments of the `#[app]` attribute -#[derive(Debug)] -pub struct AppArgs { - /// Device - pub device: Path, - - /// Peripherals - pub peripherals: bool, - - /// Interrupts used to dispatch software tasks - pub dispatchers: Dispatchers, -} - -/// The `init`-ialization function -#[derive(Debug)] -#[non_exhaustive] -pub struct Init { - /// `init` context metadata - pub args: InitArgs, - - /// Attributes that will apply to this `init` function - pub attrs: Vec<Attribute>, - - /// The name of the `#[init]` function - pub name: Ident, - - /// The context argument - pub context: Box<Pat>, - - /// The statements that make up this `init` function - pub stmts: Vec<Stmt>, - - /// The name of the user provided shared resources struct - pub user_shared_struct: Ident, - - /// The name of the user provided local resources struct - pub user_local_struct: Ident, -} - -/// `init` context metadata -#[derive(Debug)] -#[non_exhaustive] -pub struct InitArgs { - /// Local resources that can be accessed from this context - pub local_resources: LocalResources, -} - -impl Default for InitArgs { - fn default() -> Self { - Self { - local_resources: LocalResources::new(), - } - } -} - -/// The `idle` context -#[derive(Debug)] -#[non_exhaustive] -pub struct Idle { - /// `idle` context metadata - pub args: IdleArgs, - - /// Attributes that will apply to this `idle` function - pub attrs: Vec<Attribute>, - - /// The name of the `#[idle]` function - pub name: Ident, - - /// The context argument - pub context: Box<Pat>, - - /// The statements that make up this `idle` function - pub stmts: Vec<Stmt>, -} - -/// `idle` context metadata -#[derive(Debug)] -#[non_exhaustive] -pub struct IdleArgs { - /// Local resources that can be accessed from this context - pub local_resources: LocalResources, - - /// Shared resources that can be accessed from this context - pub shared_resources: SharedResources, -} - -impl Default for IdleArgs { - fn default() -> Self { - Self { - local_resources: LocalResources::new(), - shared_resources: SharedResources::new(), - } - } -} - -/// Shared resource properties -#[derive(Debug)] -pub struct SharedResourceProperties { - /// A lock free (exclusive resource) - pub lock_free: bool, -} - -/// A shared resource, defined in `#[shared]` -#[derive(Debug)] -#[non_exhaustive] -pub struct SharedResource { - /// `#[cfg]` attributes like `#[cfg(debug_assertions)]` - pub cfgs: Vec<Attribute>, - - /// `#[doc]` attributes like `/// this is a docstring` - pub docs: Vec<Attribute>, - - /// Attributes that will apply to this resource - pub attrs: Vec<Attribute>, - - /// The type of this resource - pub ty: Box<Type>, - - /// Shared resource properties - pub properties: SharedResourceProperties, -} - -/// A local resource, defined in `#[local]` -#[derive(Debug)] -#[non_exhaustive] -pub struct LocalResource { - /// `#[cfg]` attributes like `#[cfg(debug_assertions)]` - pub cfgs: Vec<Attribute>, - - /// `#[doc]` attributes like `/// this is a docstring` - pub docs: Vec<Attribute>, - - /// Attributes that will apply to this resource - pub attrs: Vec<Attribute>, - - /// The type of this resource - pub ty: Box<Type>, -} - -/// An async software task -#[derive(Debug)] -#[non_exhaustive] -pub struct SoftwareTask { - /// Software task metadata - pub args: SoftwareTaskArgs, - - /// `#[cfg]` attributes like `#[cfg(debug_assertions)]` - pub cfgs: Vec<Attribute>, - - /// Attributes that will apply to this interrupt handler - pub attrs: Vec<Attribute>, - - /// The context argument - pub context: Box<Pat>, - - /// The inputs of this software task - pub inputs: Vec<PatType>, - - /// The statements that make up the task handler - pub stmts: Vec<Stmt>, - - /// The task is declared externally - pub is_extern: bool, -} - -/// Software task metadata -#[derive(Debug)] -#[non_exhaustive] -pub struct SoftwareTaskArgs { - /// The priority of this task - pub priority: u8, - - /// Local resources that can be accessed from this context - pub local_resources: LocalResources, - - /// Shared resources that can be accessed from this context - pub shared_resources: SharedResources, -} - -impl Default for SoftwareTaskArgs { - fn default() -> Self { - Self { - priority: 1, - local_resources: LocalResources::new(), - shared_resources: SharedResources::new(), - } - } -} - -/// A hardware task -#[derive(Debug)] -#[non_exhaustive] -pub struct HardwareTask { - /// Hardware task metadata - pub args: HardwareTaskArgs, - - /// `#[cfg]` attributes like `#[cfg(debug_assertions)]` - pub cfgs: Vec<Attribute>, - - /// Attributes that will apply to this interrupt handler - pub attrs: Vec<Attribute>, - - /// The context argument - pub context: Box<Pat>, - - /// The statements that make up the task handler - pub stmts: Vec<Stmt>, - - /// The task is declared externally - pub is_extern: bool, -} - -/// Hardware task metadata -#[derive(Debug)] -#[non_exhaustive] -pub struct HardwareTaskArgs { - /// The interrupt or exception that this task is bound to - pub binds: Ident, - - /// The priority of this task - pub priority: u8, - - /// Local resources that can be accessed from this context - pub local_resources: LocalResources, - - /// Shared resources that can be accessed from this context - pub shared_resources: SharedResources, -} - -/// A `static mut` variable local to and owned by a context -#[derive(Debug)] -#[non_exhaustive] -pub struct Local { - /// Attributes like `#[link_section]` - pub attrs: Vec<Attribute>, - - /// `#[cfg]` attributes like `#[cfg(debug_assertions)]` - pub cfgs: Vec<Attribute>, - - /// Type - pub ty: Box<Type>, - - /// Initial value - pub expr: Box<Expr>, -} - -/// A wrapper of the 2 kinds of locals that tasks can have -#[derive(Debug)] -#[non_exhaustive] -pub enum TaskLocal { - /// The local is declared externally (i.e. `#[local]` struct) - External, - /// The local is declared in the task - Declared(Local), -} - -/// Resource access -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum Access { - /// `[x]`, a mutable resource - Exclusive, - - /// `[&x]`, a static non-mutable resource - Shared, -} - -impl Access { - /// Is this enum in the `Exclusive` variant? - pub fn is_exclusive(&self) -> bool { - *self == Access::Exclusive - } - - /// Is this enum in the `Shared` variant? - pub fn is_shared(&self) -> bool { - *self == Access::Shared - } -} - -/// Shared resource access list in task attribute -pub type SharedResources = Map<Access>; - -/// Local resource access/declaration list in task attribute -pub type LocalResources = Map<TaskLocal>; diff --git a/macros/src/syntax/check.rs b/macros/src/syntax/check.rs deleted file mode 100644 index 989d4180..00000000 --- a/macros/src/syntax/check.rs +++ /dev/null @@ -1,66 +0,0 @@ -use std::collections::HashSet; - -use syn::parse; - -use crate::syntax::ast::App; - -pub fn app(app: &App) -> parse::Result<()> { - // Check that all referenced resources have been declared - // Check that resources are NOT `Exclusive`-ly shared - let mut owners = HashSet::new(); - for (_, name, access) in app.shared_resource_accesses() { - if app.shared_resources.get(name).is_none() { - return Err(parse::Error::new( - name.span(), - "this shared resource has NOT been declared", - )); - } - - if access.is_exclusive() { - owners.insert(name); - } - } - - for name in app.local_resource_accesses() { - if app.local_resources.get(name).is_none() { - return Err(parse::Error::new( - name.span(), - "this local resource has NOT been declared", - )); - } - } - - // Check that no resource has both types of access (`Exclusive` & `Shared`) - let exclusive_accesses = app - .shared_resource_accesses() - .filter_map(|(priority, name, access)| { - if priority.is_some() && access.is_exclusive() { - Some(name) - } else { - None - } - }) - .collect::<HashSet<_>>(); - for (_, name, access) in app.shared_resource_accesses() { - if access.is_shared() && exclusive_accesses.contains(name) { - return Err(parse::Error::new( - name.span(), - "this implementation doesn't support shared (`&-`) - exclusive (`&mut-`) locks; use `x` instead of `&x`", - )); - } - } - - // check that dispatchers are not used as hardware tasks - for task in app.hardware_tasks.values() { - let binds = &task.args.binds; - - if app.args.dispatchers.contains_key(binds) { - return Err(parse::Error::new( - binds.span(), - "dispatcher interrupts can't be used as hardware tasks", - )); - } - } - - Ok(()) -} diff --git a/macros/src/syntax/optimize.rs b/macros/src/syntax/optimize.rs deleted file mode 100644 index e83ba31b..00000000 --- a/macros/src/syntax/optimize.rs +++ /dev/null @@ -1,36 +0,0 @@ -use std::collections::{BTreeSet, HashMap}; - -use crate::syntax::ast::App; - -pub fn app(app: &mut App, settings: &Settings) { - // "compress" priorities - // If the user specified, for example, task priorities of "1, 3, 6", - // compress them into "1, 2, 3" as to leave no gaps - if settings.optimize_priorities { - // all task priorities ordered in ascending order - let priorities = app - .hardware_tasks - .values() - .map(|task| Some(task.args.priority)) - .chain( - app.software_tasks - .values() - .map(|task| Some(task.args.priority)), - ) - .collect::<BTreeSet<_>>(); - - let map = priorities - .iter() - .cloned() - .zip(1..) - .collect::<HashMap<_, _>>(); - - for task in app.hardware_tasks.values_mut() { - task.args.priority = map[&Some(task.args.priority)]; - } - - for task in app.software_tasks.values_mut() { - task.args.priority = map[&Some(task.args.priority)]; - } - } -} diff --git a/macros/src/syntax/parse.rs b/macros/src/syntax/parse.rs deleted file mode 100644 index c78453a4..00000000 --- a/macros/src/syntax/parse.rs +++ /dev/null @@ -1,363 +0,0 @@ -mod app; -mod hardware_task; -mod idle; -mod init; -mod resource; -mod software_task; -mod util; - -use proc_macro2::TokenStream as TokenStream2; -use syn::{ - braced, parenthesized, - parse::{self, Parse, ParseStream, Parser}, - token::Brace, - Ident, Item, LitInt, Token, -}; - -use crate::syntax::{ - ast::{App, AppArgs, HardwareTaskArgs, IdleArgs, InitArgs, SoftwareTaskArgs, TaskLocal}, - Either, -}; - -// Parse the app, both app arguments and body (input) -pub fn app(args: TokenStream2, input: TokenStream2) -> parse::Result<App> { - let args = AppArgs::parse(args)?; - let input: Input = syn::parse2(input)?; - - App::parse(args, input) -} - -pub(crate) struct Input { - _mod_token: Token![mod], - pub ident: Ident, - _brace_token: Brace, - pub items: Vec<Item>, -} - -impl Parse for Input { - fn parse(input: ParseStream<'_>) -> parse::Result<Self> { - fn parse_items(input: ParseStream<'_>) -> parse::Result<Vec<Item>> { - let mut items = vec![]; - - while !input.is_empty() { - items.push(input.parse()?); - } - - Ok(items) - } - - let content; - - let _mod_token = input.parse()?; - let ident = input.parse()?; - let _brace_token = braced!(content in input); - let items = content.call(parse_items)?; - - Ok(Input { - _mod_token, - ident, - _brace_token, - items, - }) - } -} - -fn init_args(tokens: TokenStream2) -> parse::Result<InitArgs> { - (|input: ParseStream<'_>| -> parse::Result<InitArgs> { - if input.is_empty() { - return Ok(InitArgs::default()); - } - - let mut local_resources = None; - - let content; - parenthesized!(content in input); - - if !content.is_empty() { - loop { - // Parse identifier name - let ident: Ident = content.parse()?; - // Handle equal sign - let _: Token![=] = content.parse()?; - - match &*ident.to_string() { - "local" => { - if local_resources.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - local_resources = Some(util::parse_local_resources(&content)?); - } - _ => { - return Err(parse::Error::new(ident.span(), "unexpected argument")); - } - } - - if content.is_empty() { - break; - } - // Handle comma: , - let _: Token![,] = content.parse()?; - } - } - - if let Some(locals) = &local_resources { - for (ident, task_local) in locals { - if let TaskLocal::External = task_local { - return Err(parse::Error::new( - ident.span(), - "only declared local resources are allowed in init", - )); - } - } - } - - Ok(InitArgs { - local_resources: local_resources.unwrap_or_default(), - }) - }) - .parse2(tokens) -} - -fn idle_args(tokens: TokenStream2) -> parse::Result<IdleArgs> { - (|input: ParseStream<'_>| -> parse::Result<IdleArgs> { - if input.is_empty() { - return Ok(IdleArgs::default()); - } - - let mut shared_resources = None; - let mut local_resources = None; - - let content; - parenthesized!(content in input); - if !content.is_empty() { - loop { - // Parse identifier name - let ident: Ident = content.parse()?; - // Handle equal sign - let _: Token![=] = content.parse()?; - - match &*ident.to_string() { - "shared" => { - if shared_resources.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - shared_resources = Some(util::parse_shared_resources(&content)?); - } - - "local" => { - if local_resources.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - local_resources = Some(util::parse_local_resources(&content)?); - } - - _ => { - return Err(parse::Error::new(ident.span(), "unexpected argument")); - } - } - if content.is_empty() { - break; - } - - // Handle comma: , - let _: Token![,] = content.parse()?; - } - } - - Ok(IdleArgs { - shared_resources: shared_resources.unwrap_or_default(), - local_resources: local_resources.unwrap_or_default(), - }) - }) - .parse2(tokens) -} - -fn task_args(tokens: TokenStream2) -> parse::Result<Either<HardwareTaskArgs, SoftwareTaskArgs>> { - (|input: ParseStream<'_>| -> parse::Result<Either<HardwareTaskArgs, SoftwareTaskArgs>> { - if input.is_empty() { - return Ok(Either::Right(SoftwareTaskArgs::default())); - } - - let mut binds = None; - let mut capacity = None; - let mut priority = None; - let mut shared_resources = None; - let mut local_resources = None; - let mut prio_span = None; - - let content; - parenthesized!(content in input); - loop { - if content.is_empty() { - break; - } - - // Parse identifier name - let ident: Ident = content.parse()?; - let ident_s = ident.to_string(); - - // Handle equal sign - let _: Token![=] = content.parse()?; - - match &*ident_s { - "binds" => { - if binds.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - if capacity.is_some() { - return Err(parse::Error::new( - ident.span(), - "hardware tasks can't use the `capacity` argument", - )); - } - - // Parse identifier name - let ident = content.parse()?; - - binds = Some(ident); - } - - "capacity" => { - if capacity.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - if binds.is_some() { - return Err(parse::Error::new( - ident.span(), - "hardware tasks can't use the `capacity` argument", - )); - } - - // #lit - let lit: LitInt = content.parse()?; - - if !lit.suffix().is_empty() { - return Err(parse::Error::new( - lit.span(), - "this literal must be unsuffixed", - )); - } - - let value = lit.base10_parse::<u8>().ok(); - if value.is_none() || value == Some(0) { - return Err(parse::Error::new( - lit.span(), - "this literal must be in the range 1...255", - )); - } - - capacity = Some(value.unwrap()); - } - - "priority" => { - if priority.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - // #lit - let lit: LitInt = content.parse()?; - - if !lit.suffix().is_empty() { - return Err(parse::Error::new( - lit.span(), - "this literal must be unsuffixed", - )); - } - - let value = lit.base10_parse::<u8>().ok(); - if value.is_none() { - return Err(parse::Error::new( - lit.span(), - "this literal must be in the range 0...255", - )); - } - - prio_span = Some(lit.span()); - priority = Some(value.unwrap()); - } - - "shared" => { - if shared_resources.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - shared_resources = Some(util::parse_shared_resources(&content)?); - } - - "local" => { - if local_resources.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - local_resources = Some(util::parse_local_resources(&content)?); - } - - _ => { - return Err(parse::Error::new(ident.span(), "unexpected argument")); - } - } - - if content.is_empty() { - break; - } - - // Handle comma: , - let _: Token![,] = content.parse()?; - } - let priority = priority.unwrap_or(1); - let shared_resources = shared_resources.unwrap_or_default(); - let local_resources = local_resources.unwrap_or_default(); - - Ok(if let Some(binds) = binds { - if priority == 0 { - return Err(parse::Error::new( - prio_span.unwrap(), - "hardware tasks are not allowed to be at priority 0", - )); - } - - Either::Left(HardwareTaskArgs { - binds, - priority, - shared_resources, - local_resources, - }) - } else { - Either::Right(SoftwareTaskArgs { - priority, - shared_resources, - local_resources, - }) - }) - }) - .parse2(tokens) -} diff --git a/macros/src/syntax/parse/app.rs b/macros/src/syntax/parse/app.rs deleted file mode 100644 index e797f75e..00000000 --- a/macros/src/syntax/parse/app.rs +++ /dev/null @@ -1,480 +0,0 @@ -use std::collections::HashSet; - -// use indexmap::map::Entry; -use proc_macro2::TokenStream as TokenStream2; -use syn::{ - parse::{self, ParseStream, Parser}, - spanned::Spanned, - Expr, ExprArray, Fields, ForeignItem, Ident, Item, LitBool, Path, Token, Visibility, -}; - -use super::Input; -use crate::syntax::{ - ast::{ - App, AppArgs, Dispatcher, Dispatchers, HardwareTask, Idle, IdleArgs, Init, InitArgs, - LocalResource, SharedResource, SoftwareTask, - }, - parse::{self as syntax_parse, util}, - Either, Map, Set, -}; - -impl AppArgs { - pub(crate) fn parse(tokens: TokenStream2) -> parse::Result<Self> { - (|input: ParseStream<'_>| -> parse::Result<Self> { - let mut custom = Set::new(); - let mut device = None; - let mut peripherals = true; - let mut dispatchers = Dispatchers::new(); - - loop { - if input.is_empty() { - break; - } - - // #ident = .. - let ident: Ident = input.parse()?; - let _eq_token: Token![=] = input.parse()?; - - if custom.contains(&ident) { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - custom.insert(ident.clone()); - - let ks = ident.to_string(); - - match &*ks { - "device" => { - if let Ok(p) = input.parse::<Path>() { - device = Some(p); - } else { - return Err(parse::Error::new( - ident.span(), - "unexpected argument value; this should be a path", - )); - } - } - - "peripherals" => { - if let Ok(p) = input.parse::<LitBool>() { - peripherals = p.value; - } else { - return Err(parse::Error::new( - ident.span(), - "unexpected argument value; this should be a boolean", - )); - } - } - - "dispatchers" => { - if let Ok(p) = input.parse::<ExprArray>() { - for e in p.elems { - match e { - Expr::Path(ep) => { - let path = ep.path; - let ident = if path.leading_colon.is_some() - || path.segments.len() != 1 - { - return Err(parse::Error::new( - path.span(), - "interrupt must be an identifier, not a path", - )); - } else { - path.segments[0].ident.clone() - }; - let span = ident.span(); - if dispatchers.contains_key(&ident) { - return Err(parse::Error::new( - span, - "this extern interrupt is listed more than once", - )); - } else { - dispatchers - .insert(ident, Dispatcher { attrs: ep.attrs }); - } - } - _ => { - return Err(parse::Error::new( - e.span(), - "interrupt must be an identifier", - )); - } - } - } - } else { - return Err(parse::Error::new( - ident.span(), - // increasing the length of the error message will break rustfmt - "unexpected argument value; expected an array", - )); - } - } - _ => { - return Err(parse::Error::new(ident.span(), "unexpected argument")); - } - } - - if input.is_empty() { - break; - } - - // , - let _: Token![,] = input.parse()?; - } - - let device = if let Some(device) = device { - device - } else { - return Err(parse::Error::new(input.span(), "missing `device = ...`")); - }; - - Ok(AppArgs { - device, - peripherals, - dispatchers, - }) - }) - .parse2(tokens) - } -} - -impl App { - pub(crate) fn parse(args: AppArgs, input: Input) -> parse::Result<Self> { - let mut init = None; - let mut idle = None; - - let mut shared_resources_ident = None; - let mut shared_resources = Map::new(); - let mut local_resources_ident = None; - let mut local_resources = Map::new(); - let mut hardware_tasks = Map::new(); - let mut software_tasks = Map::new(); - let mut user_imports = vec![]; - let mut user_code = vec![]; - - let mut seen_idents = HashSet::<Ident>::new(); - let mut bindings = HashSet::<Ident>::new(); - - let mut check_binding = |ident: &Ident| { - if bindings.contains(ident) { - return Err(parse::Error::new( - ident.span(), - "this interrupt is already bound", - )); - } else { - bindings.insert(ident.clone()); - } - - Ok(()) - }; - - let mut check_ident = |ident: &Ident| { - if seen_idents.contains(ident) { - return Err(parse::Error::new( - ident.span(), - "this identifier has already been used", - )); - } else { - seen_idents.insert(ident.clone()); - } - - Ok(()) - }; - - for mut item in input.items { - match item { - Item::Fn(mut item) => { - let span = item.sig.ident.span(); - if let Some(pos) = item - .attrs - .iter() - .position(|attr| util::attr_eq(attr, "init")) - { - let args = InitArgs::parse(item.attrs.remove(pos).tokens)?; - - // If an init function already exists, error - if init.is_some() { - return Err(parse::Error::new( - span, - "`#[init]` function must appear at most once", - )); - } - - check_ident(&item.sig.ident)?; - - init = Some(Init::parse(args, item)?); - } else if let Some(pos) = item - .attrs - .iter() - .position(|attr| util::attr_eq(attr, "idle")) - { - let args = IdleArgs::parse(item.attrs.remove(pos).tokens)?; - - // If an idle function already exists, error - if idle.is_some() { - return Err(parse::Error::new( - span, - "`#[idle]` function must appear at most once", - )); - } - - check_ident(&item.sig.ident)?; - - idle = Some(Idle::parse(args, item)?); - } else if let Some(pos) = item - .attrs - .iter() - .position(|attr| util::attr_eq(attr, "task")) - { - if hardware_tasks.contains_key(&item.sig.ident) - || software_tasks.contains_key(&item.sig.ident) - { - return Err(parse::Error::new( - span, - "this task is defined multiple times", - )); - } - - match syntax_parse::task_args(item.attrs.remove(pos).tokens)? { - Either::Left(args) => { - check_binding(&args.binds)?; - check_ident(&item.sig.ident)?; - - hardware_tasks.insert( - item.sig.ident.clone(), - HardwareTask::parse(args, item)?, - ); - } - - Either::Right(args) => { - check_ident(&item.sig.ident)?; - - software_tasks.insert( - item.sig.ident.clone(), - SoftwareTask::parse(args, item)?, - ); - } - } - } else { - // Forward normal functions - user_code.push(Item::Fn(item.clone())); - } - } - - Item::Struct(ref mut struct_item) => { - // Match structures with the attribute #[shared], name of structure is not - // important - if let Some(_pos) = struct_item - .attrs - .iter() - .position(|attr| util::attr_eq(attr, "shared")) - { - let span = struct_item.ident.span(); - - shared_resources_ident = Some(struct_item.ident.clone()); - - if !shared_resources.is_empty() { - return Err(parse::Error::new( - span, - "`#[shared]` struct must appear at most once", - )); - } - - if struct_item.vis != Visibility::Inherited { - return Err(parse::Error::new( - struct_item.span(), - "this item must have inherited / private visibility", - )); - } - - if let Fields::Named(fields) = &mut struct_item.fields { - for field in &mut fields.named { - let ident = field.ident.as_ref().expect("UNREACHABLE"); - - if shared_resources.contains_key(ident) { - return Err(parse::Error::new( - ident.span(), - "this resource is listed more than once", - )); - } - - shared_resources.insert( - ident.clone(), - SharedResource::parse(field, ident.span())?, - ); - } - } else { - return Err(parse::Error::new( - struct_item.span(), - "this `struct` must have named fields", - )); - } - } else if let Some(_pos) = struct_item - .attrs - .iter() - .position(|attr| util::attr_eq(attr, "local")) - { - let span = struct_item.ident.span(); - - local_resources_ident = Some(struct_item.ident.clone()); - - if !local_resources.is_empty() { - return Err(parse::Error::new( - span, - "`#[local]` struct must appear at most once", - )); - } - - if struct_item.vis != Visibility::Inherited { - return Err(parse::Error::new( - struct_item.span(), - "this item must have inherited / private visibility", - )); - } - - if let Fields::Named(fields) = &mut struct_item.fields { - for field in &mut fields.named { - let ident = field.ident.as_ref().expect("UNREACHABLE"); - - if local_resources.contains_key(ident) { - return Err(parse::Error::new( - ident.span(), - "this resource is listed more than once", - )); - } - - local_resources.insert( - ident.clone(), - LocalResource::parse(field, ident.span())?, - ); - } - } else { - return Err(parse::Error::new( - struct_item.span(), - "this `struct` must have named fields", - )); - } - } else { - // Structure without the #[resources] attribute should just be passed along - user_code.push(item.clone()); - } - } - - Item::ForeignMod(mod_) => { - if !util::abi_is_rust(&mod_.abi) { - return Err(parse::Error::new( - mod_.abi.extern_token.span(), - "this `extern` block must use the \"Rust\" ABI", - )); - } - - for item in mod_.items { - if let ForeignItem::Fn(mut item) = item { - let span = item.sig.ident.span(); - if let Some(pos) = item - .attrs - .iter() - .position(|attr| util::attr_eq(attr, "task")) - { - if hardware_tasks.contains_key(&item.sig.ident) - || software_tasks.contains_key(&item.sig.ident) - { - return Err(parse::Error::new( - span, - "this task is defined multiple times", - )); - } - - if item.attrs.len() != 1 { - return Err(parse::Error::new( - span, - "`extern` task required `#[task(..)]` attribute", - )); - } - - match syntax_parse::task_args(item.attrs.remove(pos).tokens)? { - Either::Left(args) => { - check_binding(&args.binds)?; - check_ident(&item.sig.ident)?; - - hardware_tasks.insert( - item.sig.ident.clone(), - HardwareTask::parse_foreign(args, item)?, - ); - } - - Either::Right(args) => { - check_ident(&item.sig.ident)?; - - software_tasks.insert( - item.sig.ident.clone(), - SoftwareTask::parse_foreign(args, item)?, - ); - } - } - } else { - return Err(parse::Error::new( - span, - "`extern` task required `#[task(..)]` attribute", - )); - } - } else { - return Err(parse::Error::new( - item.span(), - "this item must live outside the `#[app]` module", - )); - } - } - } - Item::Use(itemuse_) => { - // Store the user provided use-statements - user_imports.push(itemuse_.clone()); - } - _ => { - // Anything else within the module should not make any difference - user_code.push(item.clone()); - } - } - } - - let shared_resources_ident = - shared_resources_ident.expect("No `#[shared]` resource struct defined"); - let local_resources_ident = - local_resources_ident.expect("No `#[local]` resource struct defined"); - let init = init.expect("No `#[init]` function defined"); - - if shared_resources_ident != init.user_shared_struct { - return Err(parse::Error::new( - init.user_shared_struct.span(), - format!( - "This name and the one defined on `#[shared]` are not the same. Should this be `{shared_resources_ident}`?" - ), - )); - } - - if local_resources_ident != init.user_local_struct { - return Err(parse::Error::new( - init.user_local_struct.span(), - format!( - "This name and the one defined on `#[local]` are not the same. Should this be `{local_resources_ident}`?" - ), - )); - } - - Ok(App { - args, - name: input.ident, - init, - idle, - shared_resources, - local_resources, - user_imports, - user_code, - hardware_tasks, - software_tasks, - }) - } -} diff --git a/macros/src/syntax/parse/hardware_task.rs b/macros/src/syntax/parse/hardware_task.rs deleted file mode 100644 index 7f6dfbe4..00000000 --- a/macros/src/syntax/parse/hardware_task.rs +++ /dev/null @@ -1,76 +0,0 @@ -use syn::{parse, ForeignItemFn, ItemFn, Stmt}; - -use crate::syntax::parse::util::FilterAttrs; -use crate::syntax::{ - ast::{HardwareTask, HardwareTaskArgs}, - parse::util, -}; - -impl HardwareTask { - pub(crate) fn parse(args: HardwareTaskArgs, item: ItemFn) -> parse::Result<Self> { - let span = item.sig.ident.span(); - let valid_signature = util::check_fn_signature(&item, false) - && item.sig.inputs.len() == 1 - && util::type_is_unit(&item.sig.output); - - let name = item.sig.ident.to_string(); - - if valid_signature { - if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) { - if rest.is_empty() { - let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs); - - return Ok(HardwareTask { - args, - cfgs, - attrs, - context, - stmts: item.block.stmts, - is_extern: false, - }); - } - } - } - - Err(parse::Error::new( - span, - format!("this task handler must have type signature `fn({name}::Context)`"), - )) - } -} - -impl HardwareTask { - pub(crate) fn parse_foreign( - args: HardwareTaskArgs, - item: ForeignItemFn, - ) -> parse::Result<Self> { - let span = item.sig.ident.span(); - let valid_signature = util::check_foreign_fn_signature(&item, false) - && item.sig.inputs.len() == 1 - && util::type_is_unit(&item.sig.output); - - let name = item.sig.ident.to_string(); - - if valid_signature { - if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) { - if rest.is_empty() { - let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs); - - return Ok(HardwareTask { - args, - cfgs, - attrs, - context, - stmts: Vec::<Stmt>::new(), - is_extern: true, - }); - } - } - } - - Err(parse::Error::new( - span, - format!("this task handler must have type signature `fn({name}::Context)`"), - )) - } -} diff --git a/macros/src/syntax/parse/idle.rs b/macros/src/syntax/parse/idle.rs deleted file mode 100644 index 124c1366..00000000 --- a/macros/src/syntax/parse/idle.rs +++ /dev/null @@ -1,42 +0,0 @@ -use proc_macro2::TokenStream as TokenStream2; -use syn::{parse, ItemFn}; - -use crate::syntax::{ - ast::{Idle, IdleArgs}, - parse::util, -}; - -impl IdleArgs { - pub(crate) fn parse(tokens: TokenStream2) -> parse::Result<Self> { - crate::syntax::parse::idle_args(tokens) - } -} - -impl Idle { - pub(crate) fn parse(args: IdleArgs, item: ItemFn) -> parse::Result<Self> { - let valid_signature = util::check_fn_signature(&item, false) - && item.sig.inputs.len() == 1 - && util::type_is_bottom(&item.sig.output); - - let name = item.sig.ident.to_string(); - - if valid_signature { - if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) { - if rest.is_empty() { - return Ok(Idle { - args, - attrs: item.attrs, - context, - name: item.sig.ident, - stmts: item.block.stmts, - }); - } - } - } - - Err(parse::Error::new( - item.sig.ident.span(), - format!("this `#[idle]` function must have signature `fn({name}::Context) -> !`"), - )) - } -} diff --git a/macros/src/syntax/parse/init.rs b/macros/src/syntax/parse/init.rs deleted file mode 100644 index 0aea20bd..00000000 --- a/macros/src/syntax/parse/init.rs +++ /dev/null @@ -1,51 +0,0 @@ -use proc_macro2::TokenStream as TokenStream2; - -use syn::{parse, ItemFn}; - -use crate::syntax::{ - ast::{Init, InitArgs}, - parse::{self as syntax_parse, util}, -}; - -impl InitArgs { - pub(crate) fn parse(tokens: TokenStream2) -> parse::Result<Self> { - syntax_parse::init_args(tokens) - } -} - -impl Init { - pub(crate) fn parse(args: InitArgs, item: ItemFn) -> parse::Result<Self> { - let valid_signature = util::check_fn_signature(&item, false) && item.sig.inputs.len() == 1; - - let span = item.sig.ident.span(); - - let name = item.sig.ident.to_string(); - - if valid_signature { - if let Ok((user_shared_struct, user_local_struct)) = - util::type_is_init_return(&item.sig.output) - { - if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) { - if rest.is_empty() { - return Ok(Init { - args, - attrs: item.attrs, - context, - name: item.sig.ident, - stmts: item.block.stmts, - user_shared_struct, - user_local_struct, - }); - } - } - } - } - - Err(parse::Error::new( - span, - format!( - "the `#[init]` function must have signature `fn({name}::Context) -> (Shared resources struct, Local resources struct)`" - ), - )) - } -} diff --git a/macros/src/syntax/parse/resource.rs b/macros/src/syntax/parse/resource.rs deleted file mode 100644 index ff100576..00000000 --- a/macros/src/syntax/parse/resource.rs +++ /dev/null @@ -1,55 +0,0 @@ -use proc_macro2::Span; -use syn::{parse, Field, Visibility}; - -use crate::syntax::parse::util::FilterAttrs; -use crate::syntax::{ - ast::{LocalResource, SharedResource, SharedResourceProperties}, - parse::util, -}; - -impl SharedResource { - pub(crate) fn parse(item: &Field, span: Span) -> parse::Result<Self> { - if item.vis != Visibility::Inherited { - return Err(parse::Error::new( - span, - "this field must have inherited / private visibility", - )); - } - - let FilterAttrs { - cfgs, - mut attrs, - docs, - } = util::filter_attributes(item.attrs.clone()); - - let lock_free = util::extract_lock_free(&mut attrs)?; - - Ok(SharedResource { - cfgs, - attrs, - docs, - ty: Box::new(item.ty.clone()), - properties: SharedResourceProperties { lock_free }, - }) - } -} - -impl LocalResource { - pub(crate) fn parse(item: &Field, span: Span) -> parse::Result<Self> { - if item.vis != Visibility::Inherited { - return Err(parse::Error::new( - span, - "this field must have inherited / private visibility", - )); - } - - let FilterAttrs { cfgs, attrs, docs } = util::filter_attributes(item.attrs.clone()); - - Ok(LocalResource { - cfgs, - attrs, - docs, - ty: Box::new(item.ty.clone()), - }) - } -} diff --git a/macros/src/syntax/parse/software_task.rs b/macros/src/syntax/parse/software_task.rs deleted file mode 100644 index 769aa653..00000000 --- a/macros/src/syntax/parse/software_task.rs +++ /dev/null @@ -1,76 +0,0 @@ -use syn::{parse, ForeignItemFn, ItemFn, Stmt}; - -use crate::syntax::parse::util::FilterAttrs; -use crate::syntax::{ - ast::{SoftwareTask, SoftwareTaskArgs}, - parse::util, -}; - -impl SoftwareTask { - pub(crate) fn parse(args: SoftwareTaskArgs, item: ItemFn) -> parse::Result<Self> { - let valid_signature = util::check_fn_signature(&item, true) - && util::type_is_unit(&item.sig.output) - && item.sig.asyncness.is_some(); - - let span = item.sig.ident.span(); - - let name = item.sig.ident.to_string(); - - if valid_signature { - if let Some((context, Ok(inputs))) = util::parse_inputs(item.sig.inputs, &name) { - let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs); - - return Ok(SoftwareTask { - args, - attrs, - cfgs, - context, - inputs, - stmts: item.block.stmts, - is_extern: false, - }); - } - } - - Err(parse::Error::new( - span, - format!("this task handler must have type signature `async fn({name}::Context, ..)`"), - )) - } -} - -impl SoftwareTask { - pub(crate) fn parse_foreign( - args: SoftwareTaskArgs, - item: ForeignItemFn, - ) -> parse::Result<Self> { - let valid_signature = util::check_foreign_fn_signature(&item, true) - && util::type_is_unit(&item.sig.output) - && item.sig.asyncness.is_some(); - - let span = item.sig.ident.span(); - - let name = item.sig.ident.to_string(); - - if valid_signature { - if let Some((context, Ok(inputs))) = util::parse_inputs(item.sig.inputs, &name) { - let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs); - - return Ok(SoftwareTask { - args, - attrs, - cfgs, - context, - inputs, - stmts: Vec::<Stmt>::new(), - is_extern: true, - }); - } - } - - Err(parse::Error::new( - span, - format!("this task handler must have type signature `async fn({name}::Context, ..)`"), - )) - } -} diff --git a/macros/src/syntax/parse/util.rs b/macros/src/syntax/parse/util.rs deleted file mode 100644 index 5a5e0c0e..00000000 --- a/macros/src/syntax/parse/util.rs +++ /dev/null @@ -1,338 +0,0 @@ -use syn::{ - bracketed, - parse::{self, ParseStream}, - punctuated::Punctuated, - spanned::Spanned, - Abi, AttrStyle, Attribute, Expr, FnArg, ForeignItemFn, Ident, ItemFn, Pat, PatType, Path, - PathArguments, ReturnType, Token, Type, Visibility, -}; - -use crate::syntax::{ - ast::{Access, Local, LocalResources, SharedResources, TaskLocal}, - Map, -}; - -pub fn abi_is_rust(abi: &Abi) -> bool { - match &abi.name { - None => true, - Some(s) => s.value() == "Rust", - } -} - -pub fn attr_eq(attr: &Attribute, name: &str) -> bool { - attr.style == AttrStyle::Outer && attr.path.segments.len() == 1 && { - let segment = attr.path.segments.first().unwrap(); - segment.arguments == PathArguments::None && *segment.ident.to_string() == *name - } -} - -/// checks that a function signature -/// -/// - has no bounds (like where clauses) -/// - is not `async` -/// - is not `const` -/// - is not `unsafe` -/// - is not generic (has no type parameters) -/// - is not variadic -/// - uses the Rust ABI (and not e.g. "C") -pub fn check_fn_signature(item: &ItemFn, allow_async: bool) -> bool { - item.vis == Visibility::Inherited - && item.sig.constness.is_none() - && (item.sig.asyncness.is_none() || allow_async) - && item.sig.abi.is_none() - && item.sig.unsafety.is_none() - && item.sig.generics.params.is_empty() - && item.sig.generics.where_clause.is_none() - && item.sig.variadic.is_none() -} - -#[allow(dead_code)] -pub fn check_foreign_fn_signature(item: &ForeignItemFn, allow_async: bool) -> bool { - item.vis == Visibility::Inherited - && item.sig.constness.is_none() - && (item.sig.asyncness.is_none() || allow_async) - && item.sig.abi.is_none() - && item.sig.unsafety.is_none() - && item.sig.generics.params.is_empty() - && item.sig.generics.where_clause.is_none() - && item.sig.variadic.is_none() -} - -pub struct FilterAttrs { - pub cfgs: Vec<Attribute>, - pub docs: Vec<Attribute>, - pub attrs: Vec<Attribute>, -} - -pub fn filter_attributes(input_attrs: Vec<Attribute>) -> FilterAttrs { - let mut cfgs = vec![]; - let mut docs = vec![]; - let mut attrs = vec![]; - - for attr in input_attrs { - if attr_eq(&attr, "cfg") { - cfgs.push(attr); - } else if attr_eq(&attr, "doc") { - docs.push(attr); - } else { - attrs.push(attr); - } - } - - FilterAttrs { cfgs, docs, attrs } -} - -pub fn extract_lock_free(attrs: &mut Vec<Attribute>) -> parse::Result<bool> { - if let Some(pos) = attrs.iter().position(|attr| attr_eq(attr, "lock_free")) { - attrs.remove(pos); - Ok(true) - } else { - Ok(false) - } -} - -pub fn parse_shared_resources(content: ParseStream<'_>) -> parse::Result<SharedResources> { - let inner; - bracketed!(inner in content); - - let mut resources = Map::new(); - for e in inner.call(Punctuated::<Expr, Token![,]>::parse_terminated)? { - let err = Err(parse::Error::new( - e.span(), - "identifier appears more than once in list", - )); - let (access, path) = match e { - Expr::Path(e) => (Access::Exclusive, e.path), - - Expr::Reference(ref r) if r.mutability.is_none() => match &*r.expr { - Expr::Path(e) => (Access::Shared, e.path.clone()), - - _ => return err, - }, - - _ => return err, - }; - - let ident = extract_resource_name_ident(path)?; - - if resources.contains_key(&ident) { - return Err(parse::Error::new( - ident.span(), - "resource appears more than once in list", - )); - } - - resources.insert(ident, access); - } - - Ok(resources) -} - -fn extract_resource_name_ident(path: Path) -> parse::Result<Ident> { - if path.leading_colon.is_some() - || path.segments.len() != 1 - || path.segments[0].arguments != PathArguments::None - { - Err(parse::Error::new( - path.span(), - "resource must be an identifier, not a path", - )) - } else { - Ok(path.segments[0].ident.clone()) - } -} - -pub fn parse_local_resources(content: ParseStream<'_>) -> parse::Result<LocalResources> { - let inner; - bracketed!(inner in content); - - let mut resources = Map::new(); - - for e in inner.call(Punctuated::<Expr, Token![,]>::parse_terminated)? { - let err = Err(parse::Error::new( - e.span(), - "identifier appears more than once in list", - )); - - let (name, local) = match e { - // local = [IDENT], - Expr::Path(path) => { - if !path.attrs.is_empty() { - return Err(parse::Error::new( - path.span(), - "attributes are not supported here", - )); - } - - let ident = extract_resource_name_ident(path.path)?; - // let (cfgs, attrs) = extract_cfgs(path.attrs); - - (ident, TaskLocal::External) - } - - // local = [IDENT: TYPE = EXPR] - Expr::Assign(e) => { - let (name, ty, cfgs, attrs) = match *e.left { - Expr::Type(t) => { - // Extract name and attributes - let (name, cfgs, attrs) = match *t.expr { - Expr::Path(path) => { - let name = extract_resource_name_ident(path.path)?; - let FilterAttrs { cfgs, attrs, .. } = filter_attributes(path.attrs); - - (name, cfgs, attrs) - } - _ => return err, - }; - - let ty = t.ty; - - // Error check - match &*ty { - Type::Array(_) => {} - Type::Path(_) => {} - Type::Ptr(_) => {} - Type::Tuple(_) => {} - _ => return Err(parse::Error::new( - ty.span(), - "unsupported type, must be an array, tuple, pointer or type path", - )), - }; - - (name, ty, cfgs, attrs) - } - e => return Err(parse::Error::new(e.span(), "malformed, expected a type")), - }; - - let expr = e.right; // Expr - - ( - name, - TaskLocal::Declared(Local { - attrs, - cfgs, - ty, - expr, - }), - ) - } - - expr => { - return Err(parse::Error::new( - expr.span(), - "malformed, expected 'IDENT: TYPE = EXPR'", - )) - } - }; - - resources.insert(name, local); - } - - Ok(resources) -} - -type ParseInputResult = Option<(Box<Pat>, Result<Vec<PatType>, FnArg>)>; - -pub fn parse_inputs(inputs: Punctuated<FnArg, Token![,]>, name: &str) -> ParseInputResult { - let mut inputs = inputs.into_iter(); - - match inputs.next() { - Some(FnArg::Typed(first)) => { - if type_is_path(&first.ty, &[name, "Context"]) { - let rest = inputs - .map(|arg| match arg { - FnArg::Typed(arg) => Ok(arg), - _ => Err(arg), - }) - .collect::<Result<Vec<_>, _>>(); - - Some((first.pat, rest)) - } else { - None - } - } - - _ => None, - } -} - -pub fn type_is_bottom(ty: &ReturnType) -> bool { - if let ReturnType::Type(_, ty) = ty { - matches!(**ty, Type::Never(_)) - } else { - false - } -} - -fn extract_init_resource_name_ident(ty: Type) -> Result<Ident, ()> { - match ty { - Type::Path(path) => { - let path = path.path; - - if path.leading_colon.is_some() - || path.segments.len() != 1 - || path.segments[0].arguments != PathArguments::None - { - Err(()) - } else { - Ok(path.segments[0].ident.clone()) - } - } - _ => Err(()), - } -} - -/// Checks Init's return type, return the user provided types for analysis -pub fn type_is_init_return(ty: &ReturnType) -> Result<(Ident, Ident), ()> { - match ty { - ReturnType::Default => Err(()), - - ReturnType::Type(_, ty) => match &**ty { - Type::Tuple(t) => { - // return should be: - // fn -> (User's #[shared] struct, User's #[local] struct) - // - // We check the length and the last one here, analysis checks that the user - // provided structs are correct. - if t.elems.len() == 2 { - return Ok(( - extract_init_resource_name_ident(t.elems[0].clone())?, - extract_init_resource_name_ident(t.elems[1].clone())?, - )); - } - - Err(()) - } - - _ => Err(()), - }, - } -} - -pub fn type_is_path(ty: &Type, segments: &[&str]) -> bool { - match ty { - Type::Path(tpath) if tpath.qself.is_none() => { - tpath.path.segments.len() == segments.len() - && tpath - .path - .segments - .iter() - .zip(segments) - .all(|(lhs, rhs)| lhs.ident == **rhs) - } - - _ => false, - } -} - -pub fn type_is_unit(ty: &ReturnType) -> bool { - if let ReturnType::Type(_, ty) = ty { - if let Type::Tuple(ref tuple) = **ty { - tuple.elems.is_empty() - } else { - false - } - } else { - true - } -} diff --git a/macros/src/tests.rs b/macros/src/tests.rs deleted file mode 100644 index e9e3326e..00000000 --- a/macros/src/tests.rs +++ /dev/null @@ -1,4 +0,0 @@ -// NOTE these tests are specific to the Cortex-M port; `rtic-syntax` has a more extensive test suite -// that tests functionality common to all the RTIC ports - -mod single; diff --git a/macros/src/tests/single.rs b/macros/src/tests/single.rs deleted file mode 100644 index f20c9ccb..00000000 --- a/macros/src/tests/single.rs +++ /dev/null @@ -1,40 +0,0 @@ -use quote::quote; -use rtic_syntax::Settings; - -#[test] -fn analyze() { - let mut settings = Settings::default(); - settings.parse_extern_interrupt = true; - let (app, analysis) = rtic_syntax::parse2( - // First interrupt is assigned to the highest priority dispatcher - quote!(device = pac, dispatchers = [B, A]), - quote!( - mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { - (Shared {}, Local {}, init::Monotonics()) - } - - #[task(priority = 1)] - fn a(_: a::Context) {} - - #[task(priority = 2)] - fn b(_: b::Context) {} - } - ), - settings, - ) - .unwrap(); - - let analysis = crate::analyze::app(analysis, &app); - let interrupts = &analysis.interrupts; - assert_eq!(interrupts.len(), 2); - assert_eq!(interrupts[&2].0.to_string(), "B"); - assert_eq!(interrupts[&1].0.to_string(), "A"); -} |