mirror of
https://github.com/rtic-rs/rtic.git
synced 2024-12-29 13:29:34 +01:00
RTIC v2: Initial commit
rtic-syntax is now part of RTIC repository
This commit is contained in:
parent
d43c2b64cc
commit
4c2c05a801
167 changed files with 5219 additions and 602 deletions
14
Cargo.toml
14
Cargo.toml
|
@ -1,27 +1,29 @@
|
||||||
[package]
|
[package]
|
||||||
authors = [
|
authors = [
|
||||||
"The Real-Time Interrupt-driven Concurrency developers",
|
"The Real-Time Interrupt-driven Concurrency developers",
|
||||||
|
"Emil Fresk <emil.fresk@gmail.com>",
|
||||||
|
"Henrik Tjäder <henrik@tjaders.com>",
|
||||||
"Jorge Aparicio <jorge@japaric.io>",
|
"Jorge Aparicio <jorge@japaric.io>",
|
||||||
"Per Lindgren <per.lindgren@ltu.se>",
|
"Per Lindgren <per.lindgren@ltu.se>",
|
||||||
]
|
]
|
||||||
categories = ["concurrency", "embedded", "no-std"]
|
categories = ["concurrency", "embedded", "no-std", "asynchronous"]
|
||||||
description = "Real-Time Interrupt-driven Concurrency (RTIC): a concurrency framework for building real-time systems"
|
description = "Real-Time Interrupt-driven Concurrency (RTIC): a concurrency framework for building real-time systems"
|
||||||
documentation = "https://rtic.rs/"
|
documentation = "https://rtic.rs/"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
keywords = ["arm", "cortex-m"]
|
keywords = ["arm", "cortex-m", "risc-v", "embedded", "async", "runtime", "futures", "await", "no-std", "rtos", "bare-metal"]
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
name = "cortex-m-rtic"
|
name = "rtic"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://github.com/rtic-rs/cortex-m-rtic"
|
repository = "https://github.com/rtic-rs/rtic"
|
||||||
|
|
||||||
version = "1.1.3"
|
version = "2.0.0-alpha.0"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "rtic"
|
name = "rtic"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cortex-m = "0.7.0"
|
cortex-m = "0.7.0"
|
||||||
cortex-m-rtic-macros = { path = "macros", version = "1.1.5" }
|
rtic-macros = { path = "macros", version = "2.0.0-alpha.0" }
|
||||||
rtic-monotonic = "1.0.0"
|
rtic-monotonic = "1.0.0"
|
||||||
rtic-core = "1.0.0"
|
rtic-core = "1.0.0"
|
||||||
heapless = "0.7.7"
|
heapless = "0.7.7"
|
||||||
|
|
2
macros/.gitignore
vendored
Normal file
2
macros/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
/Cargo.lock
|
|
@ -1,28 +1,41 @@
|
||||||
[package]
|
[package]
|
||||||
authors = [
|
authors = [
|
||||||
"The Real-Time Interrupt-driven Concurrency developers",
|
"The Real-Time Interrupt-driven Concurrency developers",
|
||||||
|
"Emil Fresk <emil.fresk@gmail.com>",
|
||||||
|
"Henrik Tjäder <henrik@tjaders.com>",
|
||||||
"Jorge Aparicio <jorge@japaric.io>",
|
"Jorge Aparicio <jorge@japaric.io>",
|
||||||
|
"Per Lindgren <per.lindgren@ltu.se>",
|
||||||
]
|
]
|
||||||
categories = ["concurrency", "embedded", "no-std"]
|
categories = ["concurrency", "embedded", "no-std", "asynchronous"]
|
||||||
description = "Procedural macros of the cortex-m-rtic crate"
|
description = "Procedural macros, syntax parsing, and codegen of the RTIC crate"
|
||||||
documentation = "https://rtic-rs.github.io/cortex-m-rtic/api/cortex_m_rtic"
|
documentation = "https://rtic-rs.github.io/rtic/api/rtic"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
keywords = ["arm", "cortex-m"]
|
keywords = ["arm", "cortex-m", "risc-v", "embedded", "async", "runtime", "futures", "await", "no-std", "rtos", "bare-metal"]
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
name = "cortex-m-rtic-macros"
|
name = "rtic-macros"
|
||||||
readme = "../README.md"
|
readme = "../README.md"
|
||||||
repository = "https://github.com/rtic-rs/cortex-m-rtic"
|
repository = "https://github.com/rtic-rs/rtic"
|
||||||
version = "1.1.5"
|
|
||||||
|
version = "2.0.0-alpha.0"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[feature]
|
||||||
proc-macro2 = "1"
|
default = []
|
||||||
proc-macro-error = "1"
|
|
||||||
quote = "1"
|
|
||||||
syn = "1"
|
|
||||||
rtic-syntax = "1.0.2"
|
|
||||||
|
|
||||||
[features]
|
|
||||||
debugprint = []
|
debugprint = []
|
||||||
|
# list of supported codegen backends
|
||||||
|
thumbv6 = []
|
||||||
|
thumbv7 = []
|
||||||
|
# riscv-clic = []
|
||||||
|
# riscv-ch32 = []
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
indexmap = "1.9.2"
|
||||||
|
proc-macro2 = "1.0.49"
|
||||||
|
proc-macro-error = "1.0.4"
|
||||||
|
quote = "1.0.23"
|
||||||
|
syn = { version = "1.0.107", features = ["extra-traits", "full"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
trybuild = "1.0.73"
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
use core::ops;
|
use core::ops;
|
||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
|
||||||
use rtic_syntax::{
|
use crate::syntax::{
|
||||||
analyze::{self, Priority},
|
analyze::{self, Priority},
|
||||||
ast::{App, ExternInterrupt},
|
ast::{App, Dispatcher},
|
||||||
P,
|
|
||||||
};
|
};
|
||||||
use syn::Ident;
|
use syn::Ident;
|
||||||
|
|
||||||
/// Extend the upstream `Analysis` struct with our field
|
/// Extend the upstream `Analysis` struct with our field
|
||||||
pub struct Analysis {
|
pub struct Analysis {
|
||||||
parent: P<analyze::Analysis>,
|
parent: analyze::Analysis,
|
||||||
pub interrupts: BTreeMap<Priority, (Ident, ExternInterrupt)>,
|
pub interrupts_normal: BTreeMap<Priority, (Ident, Dispatcher)>,
|
||||||
|
pub interrupts_async: BTreeMap<Priority, (Ident, Dispatcher)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ops::Deref for Analysis {
|
impl ops::Deref for Analysis {
|
||||||
|
@ -23,25 +23,43 @@ impl ops::Deref for Analysis {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assign an interrupt to each priority level
|
// Assign an interrupt to each priority level
|
||||||
pub fn app(analysis: P<analyze::Analysis>, app: &App) -> P<Analysis> {
|
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)
|
// the set of priorities (each priority only once)
|
||||||
let priorities = app
|
let priorities = app
|
||||||
.software_tasks
|
.software_tasks
|
||||||
.values()
|
.values()
|
||||||
|
.filter(|task| !task.is_async)
|
||||||
|
.map(|task| task.args.priority)
|
||||||
|
.collect::<BTreeSet<_>>();
|
||||||
|
|
||||||
|
let priorities_async = app
|
||||||
|
.software_tasks
|
||||||
|
.values()
|
||||||
|
.filter(|task| task.is_async)
|
||||||
.map(|task| task.args.priority)
|
.map(|task| task.args.priority)
|
||||||
.collect::<BTreeSet<_>>();
|
.collect::<BTreeSet<_>>();
|
||||||
|
|
||||||
// map from priorities to interrupts (holding name and attributes)
|
// map from priorities to interrupts (holding name and attributes)
|
||||||
let interrupts: BTreeMap<Priority, _> = priorities
|
|
||||||
|
let interrupts_normal: BTreeMap<Priority, _> = priorities
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.copied()
|
||||||
.rev()
|
.rev()
|
||||||
.zip(&app.args.extern_interrupts)
|
.map(|p| (p, available_interrupt.pop().expect("UNREACHABLE")))
|
||||||
.map(|(p, (id, ext))| (p, (id.clone(), ext.clone())))
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
P::new(Analysis {
|
let interrupts_async: BTreeMap<Priority, _> = priorities_async
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.rev()
|
||||||
|
.map(|p| (p, available_interrupt.pop().expect("UNREACHABLE")))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Analysis {
|
||||||
parent: analysis,
|
parent: analysis,
|
||||||
interrupts,
|
interrupts_normal,
|
||||||
})
|
interrupts_async,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
0
macros/src/bindings.rs
Normal file
0
macros/src/bindings.rs
Normal file
|
@ -1,10 +1,11 @@
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use rtic_syntax::ast::App;
|
|
||||||
|
|
||||||
use crate::{analyze::Analysis, check::Extra};
|
use crate::analyze::Analysis;
|
||||||
|
use crate::syntax::ast::App;
|
||||||
|
|
||||||
mod assertions;
|
mod assertions;
|
||||||
|
mod async_dispatchers;
|
||||||
mod dispatchers;
|
mod dispatchers;
|
||||||
mod hardware_tasks;
|
mod hardware_tasks;
|
||||||
mod idle;
|
mod idle;
|
||||||
|
@ -12,6 +13,7 @@ mod init;
|
||||||
mod local_resources;
|
mod local_resources;
|
||||||
mod local_resources_struct;
|
mod local_resources_struct;
|
||||||
mod module;
|
mod module;
|
||||||
|
mod monotonic;
|
||||||
mod post_init;
|
mod post_init;
|
||||||
mod pre_init;
|
mod pre_init;
|
||||||
mod shared_resources;
|
mod shared_resources;
|
||||||
|
@ -21,22 +23,22 @@ mod timer_queue;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
pub fn app(app: &App, analysis: &Analysis, extra: &Extra) -> TokenStream2 {
|
pub fn app(app: &App, analysis: &Analysis) -> TokenStream2 {
|
||||||
let mut mod_app = vec![];
|
let mut mod_app = vec![];
|
||||||
let mut mains = vec![];
|
let mut mains = vec![];
|
||||||
let mut root = vec![];
|
let mut root = vec![];
|
||||||
let mut user = vec![];
|
let mut user = vec![];
|
||||||
|
|
||||||
// Generate the `main` function
|
// Generate the `main` function
|
||||||
let assertion_stmts = assertions::codegen(app, analysis, extra);
|
let assertion_stmts = assertions::codegen(app, analysis);
|
||||||
|
|
||||||
let pre_init_stmts = pre_init::codegen(app, analysis, extra);
|
let pre_init_stmts = pre_init::codegen(app, analysis);
|
||||||
|
|
||||||
let (mod_app_init, root_init, user_init, call_init) = init::codegen(app, analysis, extra);
|
let (mod_app_init, root_init, user_init, call_init) = init::codegen(app, analysis);
|
||||||
|
|
||||||
let post_init_stmts = post_init::codegen(app, analysis);
|
let post_init_stmts = post_init::codegen(app, analysis);
|
||||||
|
|
||||||
let (mod_app_idle, root_idle, user_idle, call_idle) = idle::codegen(app, analysis, extra);
|
let (mod_app_idle, root_idle, user_idle, call_idle) = idle::codegen(app, analysis);
|
||||||
|
|
||||||
user.push(quote!(
|
user.push(quote!(
|
||||||
#user_init
|
#user_init
|
||||||
|
@ -84,82 +86,25 @@ pub fn app(app: &App, analysis: &Analysis, extra: &Extra) -> TokenStream2 {
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
|
||||||
let (mod_app_shared_resources, mod_shared_resources) =
|
let (mod_app_shared_resources, mod_shared_resources) = shared_resources::codegen(app, analysis);
|
||||||
shared_resources::codegen(app, analysis, extra);
|
let (mod_app_local_resources, mod_local_resources) = local_resources::codegen(app, analysis);
|
||||||
let (mod_app_local_resources, mod_local_resources) =
|
|
||||||
local_resources::codegen(app, analysis, extra);
|
|
||||||
|
|
||||||
let (mod_app_hardware_tasks, root_hardware_tasks, user_hardware_tasks) =
|
let (mod_app_hardware_tasks, root_hardware_tasks, user_hardware_tasks) =
|
||||||
hardware_tasks::codegen(app, analysis, extra);
|
hardware_tasks::codegen(app, analysis);
|
||||||
|
|
||||||
let (mod_app_software_tasks, root_software_tasks, user_software_tasks) =
|
let (mod_app_software_tasks, root_software_tasks, user_software_tasks) =
|
||||||
software_tasks::codegen(app, analysis, extra);
|
software_tasks::codegen(app, analysis);
|
||||||
|
|
||||||
let mod_app_dispatchers = dispatchers::codegen(app, analysis, extra);
|
let monotonics = monotonic::codegen(app, analysis);
|
||||||
let mod_app_timer_queue = timer_queue::codegen(app, analysis, extra);
|
|
||||||
|
let mod_app_dispatchers = dispatchers::codegen(app, analysis);
|
||||||
|
let mod_app_async_dispatchers = async_dispatchers::codegen(app, analysis);
|
||||||
|
let mod_app_timer_queue = timer_queue::codegen(app, analysis);
|
||||||
let user_imports = &app.user_imports;
|
let user_imports = &app.user_imports;
|
||||||
let user_code = &app.user_code;
|
let user_code = &app.user_code;
|
||||||
let name = &app.name;
|
let name = &app.name;
|
||||||
let device = &extra.device;
|
let device = &app.args.device;
|
||||||
|
|
||||||
let monotonic_parts: Vec<_> = app
|
|
||||||
.monotonics
|
|
||||||
.iter()
|
|
||||||
.map(|(_, monotonic)| {
|
|
||||||
let name = &monotonic.ident;
|
|
||||||
let name_str = &name.to_string();
|
|
||||||
let cfgs = &monotonic.cfgs;
|
|
||||||
let ident = util::monotonic_ident(name_str);
|
|
||||||
let doc = &format!(
|
|
||||||
"This module holds the static implementation for `{}::now()`",
|
|
||||||
name_str
|
|
||||||
);
|
|
||||||
|
|
||||||
let default_monotonic = if monotonic.args.default {
|
|
||||||
quote!(
|
|
||||||
#(#cfgs)*
|
|
||||||
pub use #name::now;
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
quote!()
|
|
||||||
};
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
#default_monotonic
|
|
||||||
|
|
||||||
#[doc = #doc]
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
#(#cfgs)*
|
|
||||||
pub mod #name {
|
|
||||||
|
|
||||||
/// Read the current time from this monotonic
|
|
||||||
pub fn now() -> <super::super::#name as rtic::Monotonic>::Instant {
|
|
||||||
rtic::export::interrupt::free(|_| {
|
|
||||||
use rtic::Monotonic as _;
|
|
||||||
if let Some(m) = unsafe{ &mut *super::super::#ident.get_mut() } {
|
|
||||||
m.now()
|
|
||||||
} else {
|
|
||||||
<super::super::#name as rtic::Monotonic>::zero()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let monotonics = if monotonic_parts.is_empty() {
|
|
||||||
quote!()
|
|
||||||
} else {
|
|
||||||
quote!(
|
|
||||||
pub use rtic::Monotonic as _;
|
|
||||||
|
|
||||||
/// Holds static methods for each monotonic.
|
|
||||||
pub mod monotonics {
|
|
||||||
#(#monotonic_parts)*
|
|
||||||
}
|
|
||||||
)
|
|
||||||
};
|
|
||||||
let rt_err = util::rt_err_ident();
|
let rt_err = util::rt_err_ident();
|
||||||
|
|
||||||
quote!(
|
quote!(
|
||||||
|
@ -205,6 +150,8 @@ pub fn app(app: &App, analysis: &Analysis, extra: &Extra) -> TokenStream2 {
|
||||||
|
|
||||||
#(#mod_app_dispatchers)*
|
#(#mod_app_dispatchers)*
|
||||||
|
|
||||||
|
#(#mod_app_async_dispatchers)*
|
||||||
|
|
||||||
#(#mod_app_timer_queue)*
|
#(#mod_app_timer_queue)*
|
||||||
|
|
||||||
#(#mains)*
|
#(#mains)*
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
|
||||||
use crate::{analyze::Analysis, check::Extra, codegen::util};
|
use crate::syntax::ast::App;
|
||||||
use rtic_syntax::ast::App;
|
use crate::{analyze::Analysis, codegen::util};
|
||||||
|
|
||||||
/// Generates compile-time assertions that check that types implement the `Send` / `Sync` traits
|
/// Generates compile-time assertions that check that types implement the `Send` / `Sync` traits
|
||||||
pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec<TokenStream2> {
|
pub fn codegen(app: &App, analysis: &Analysis) -> Vec<TokenStream2> {
|
||||||
let mut stmts = vec![];
|
let mut stmts = vec![];
|
||||||
|
|
||||||
for ty in &analysis.send_types {
|
for ty in &analysis.send_types {
|
||||||
|
@ -21,7 +21,7 @@ pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec<TokenStream
|
||||||
stmts.push(quote!(rtic::export::assert_monotonic::<#ty>();));
|
stmts.push(quote!(rtic::export::assert_monotonic::<#ty>();));
|
||||||
}
|
}
|
||||||
|
|
||||||
let device = &extra.device;
|
let device = &app.args.device;
|
||||||
let chunks_name = util::priority_mask_chunks_ident();
|
let chunks_name = util::priority_mask_chunks_ident();
|
||||||
let no_basepri_checks: Vec<_> = app
|
let no_basepri_checks: Vec<_> = app
|
||||||
.hardware_tasks
|
.hardware_tasks
|
||||||
|
@ -29,9 +29,7 @@ pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec<TokenStream
|
||||||
.filter_map(|(_, task)| {
|
.filter_map(|(_, task)| {
|
||||||
if !util::is_exception(&task.args.binds) {
|
if !util::is_exception(&task.args.binds) {
|
||||||
let interrupt_name = &task.args.binds;
|
let interrupt_name = &task.args.binds;
|
||||||
let cfgs = &task.cfgs;
|
|
||||||
Some(quote!(
|
Some(quote!(
|
||||||
#(#cfgs)*
|
|
||||||
if (#device::Interrupt::#interrupt_name as usize) >= (#chunks_name * 32) {
|
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");
|
::core::panic!("An interrupt out of range is used while in armv6 or armv8m.base");
|
||||||
}
|
}
|
||||||
|
|
129
macros/src/codegen/async_dispatchers.rs
Normal file
129
macros/src/codegen/async_dispatchers.rs
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
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) -> Vec<TokenStream2> {
|
||||||
|
let mut items = vec![];
|
||||||
|
|
||||||
|
let interrupts = &analysis.interrupts_async;
|
||||||
|
|
||||||
|
// Generate executor definition and priority in global scope
|
||||||
|
for (name, task) in app.software_tasks.iter() {
|
||||||
|
if task.is_async {
|
||||||
|
let type_name = util::internal_task_ident(name, "F");
|
||||||
|
let exec_name = util::internal_task_ident(name, "EXEC");
|
||||||
|
let prio_name = util::internal_task_ident(name, "PRIORITY");
|
||||||
|
|
||||||
|
items.push(quote!(
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
type #type_name = impl core::future::Future + 'static;
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
|
static #exec_name:
|
||||||
|
rtic::RacyCell<rtic::export::executor::AsyncTaskExecutor<#type_name>> =
|
||||||
|
rtic::RacyCell::new(rtic::export::executor::AsyncTaskExecutor::new());
|
||||||
|
|
||||||
|
// The executors priority, this can be any value - we will overwrite it when we
|
||||||
|
// start a task
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
|
static #prio_name: rtic::RacyCell<rtic::export::Priority> =
|
||||||
|
unsafe { rtic::RacyCell::new(rtic::export::Priority::new(0)) };
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (&level, channel) in &analysis.channels {
|
||||||
|
if channel
|
||||||
|
.tasks
|
||||||
|
.iter()
|
||||||
|
.map(|task_name| !app.software_tasks[task_name].is_async)
|
||||||
|
.all(|is_not_async| is_not_async)
|
||||||
|
{
|
||||||
|
// check if all tasks are not async, if so don't generate this.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut stmts = vec![];
|
||||||
|
let device = &app.args.device;
|
||||||
|
let enum_ = util::interrupt_ident();
|
||||||
|
let interrupt = util::suffixed(&interrupts[&level].0.to_string());
|
||||||
|
|
||||||
|
for name in channel
|
||||||
|
.tasks
|
||||||
|
.iter()
|
||||||
|
.filter(|name| app.software_tasks[*name].is_async)
|
||||||
|
{
|
||||||
|
let exec_name = util::internal_task_ident(name, "EXEC");
|
||||||
|
let prio_name = util::internal_task_ident(name, "PRIORITY");
|
||||||
|
let task = &app.software_tasks[name];
|
||||||
|
// let cfgs = &task.cfgs;
|
||||||
|
let (_, tupled, pats, input_types) = util::regroup_inputs(&task.inputs);
|
||||||
|
let executor_run_ident = util::executor_run_ident(name);
|
||||||
|
|
||||||
|
let n = util::capacity_literal(channel.capacity as usize + 1);
|
||||||
|
let rq = util::rq_async_ident(name);
|
||||||
|
let (rq_ty, rq_expr) = {
|
||||||
|
(
|
||||||
|
quote!(rtic::export::ASYNCRQ<#input_types, #n>),
|
||||||
|
quote!(rtic::export::Queue::new()),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
items.push(quote!(
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
|
static #rq: rtic::RacyCell<#rq_ty> = rtic::RacyCell::new(#rq_expr);
|
||||||
|
));
|
||||||
|
|
||||||
|
stmts.push(quote!(
|
||||||
|
if !(&*#exec_name.get()).is_running() {
|
||||||
|
if let Some(#tupled) = rtic::export::interrupt::free(|_| (&mut *#rq.get_mut()).dequeue()) {
|
||||||
|
|
||||||
|
// The async executor needs a static priority
|
||||||
|
#prio_name.get_mut().write(rtic::export::Priority::new(PRIORITY));
|
||||||
|
let priority: &'static _ = &*#prio_name.get();
|
||||||
|
|
||||||
|
(&mut *#exec_name.get_mut()).spawn(#name(#name::Context::new(priority) #(,#pats)*));
|
||||||
|
#executor_run_ident.store(true, core::sync::atomic::Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if #executor_run_ident.load(core::sync::atomic::Ordering::Relaxed) {
|
||||||
|
#executor_run_ident.store(false, core::sync::atomic::Ordering::Relaxed);
|
||||||
|
if (&mut *#exec_name.get_mut()).poll(|| {
|
||||||
|
#executor_run_ident.store(true, core::sync::atomic::Ordering::Release);
|
||||||
|
rtic::pend(#device::#enum_::#interrupt);
|
||||||
|
}) && !rtic::export::interrupt::free(|_| (&*#rq.get_mut()).is_empty()) {
|
||||||
|
// If the ready queue is not empty and the executor finished, restart this
|
||||||
|
// dispatch to check if the executor should be restarted.
|
||||||
|
rtic::pend(#device::#enum_::#interrupt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let doc = format!(
|
||||||
|
"Interrupt handler to dispatch async tasks at priority {}",
|
||||||
|
level
|
||||||
|
);
|
||||||
|
let attribute = &interrupts[&level].1.attrs;
|
||||||
|
items.push(quote!(
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[doc = #doc]
|
||||||
|
#[no_mangle]
|
||||||
|
#(#attribute)*
|
||||||
|
unsafe fn #interrupt() {
|
||||||
|
/// The priority of this interrupt handler
|
||||||
|
const PRIORITY: u8 = #level;
|
||||||
|
|
||||||
|
rtic::export::run(PRIORITY, || {
|
||||||
|
#(#stmts)*
|
||||||
|
});
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
items
|
||||||
|
}
|
|
@ -1,21 +1,31 @@
|
||||||
|
use crate::syntax::ast::App;
|
||||||
|
use crate::{analyze::Analysis, codegen::util};
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use rtic_syntax::ast::App;
|
|
||||||
|
|
||||||
use crate::{analyze::Analysis, check::Extra, codegen::util};
|
|
||||||
|
|
||||||
/// Generates task dispatchers
|
/// Generates task dispatchers
|
||||||
pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec<TokenStream2> {
|
pub fn codegen(app: &App, analysis: &Analysis) -> Vec<TokenStream2> {
|
||||||
let mut items = vec![];
|
let mut items = vec![];
|
||||||
|
|
||||||
let interrupts = &analysis.interrupts;
|
let interrupts = &analysis.interrupts_normal;
|
||||||
|
|
||||||
for (&level, channel) in &analysis.channels {
|
for (&level, channel) in &analysis.channels {
|
||||||
|
if channel
|
||||||
|
.tasks
|
||||||
|
.iter()
|
||||||
|
.map(|task_name| app.software_tasks[task_name].is_async)
|
||||||
|
.all(|is_async| is_async)
|
||||||
|
{
|
||||||
|
// check if all tasks are async, if so don't generate this.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let mut stmts = vec![];
|
let mut stmts = vec![];
|
||||||
|
|
||||||
let variants = channel
|
let variants = channel
|
||||||
.tasks
|
.tasks
|
||||||
.iter()
|
.iter()
|
||||||
|
.filter(|name| !app.software_tasks[*name].is_async)
|
||||||
.map(|name| {
|
.map(|name| {
|
||||||
let cfgs = &app.software_tasks[name].cfgs;
|
let cfgs = &app.software_tasks[name].cfgs;
|
||||||
|
|
||||||
|
@ -45,6 +55,7 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec<TokenStrea
|
||||||
|
|
||||||
let n = util::capacity_literal(channel.capacity as usize + 1);
|
let n = util::capacity_literal(channel.capacity as usize + 1);
|
||||||
let rq = util::rq_ident(level);
|
let rq = util::rq_ident(level);
|
||||||
|
// let (_, _, _, input_ty) = util::regroup_inputs(inputs);
|
||||||
let (rq_ty, rq_expr) = {
|
let (rq_ty, rq_expr) = {
|
||||||
(
|
(
|
||||||
quote!(rtic::export::SCRQ<#t, #n>),
|
quote!(rtic::export::SCRQ<#t, #n>),
|
||||||
|
@ -64,6 +75,13 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec<TokenStrea
|
||||||
static #rq: rtic::RacyCell<#rq_ty> = rtic::RacyCell::new(#rq_expr);
|
static #rq: rtic::RacyCell<#rq_ty> = rtic::RacyCell::new(#rq_expr);
|
||||||
));
|
));
|
||||||
|
|
||||||
|
let interrupt = util::suffixed(
|
||||||
|
&interrupts
|
||||||
|
.get(&level)
|
||||||
|
.expect("RTIC-ICE: Unable to get interrrupt")
|
||||||
|
.0
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
let arms = channel
|
let arms = channel
|
||||||
.tasks
|
.tasks
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -74,6 +92,7 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec<TokenStrea
|
||||||
let inputs = util::inputs_ident(name);
|
let inputs = util::inputs_ident(name);
|
||||||
let (_, tupled, pats, _) = util::regroup_inputs(&task.inputs);
|
let (_, tupled, pats, _) = util::regroup_inputs(&task.inputs);
|
||||||
|
|
||||||
|
if !task.is_async {
|
||||||
quote!(
|
quote!(
|
||||||
#(#cfgs)*
|
#(#cfgs)*
|
||||||
#t::#name => {
|
#t::#name => {
|
||||||
|
@ -91,6 +110,9 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec<TokenStrea
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
quote!()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
@ -103,7 +125,6 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec<TokenStrea
|
||||||
));
|
));
|
||||||
|
|
||||||
let doc = format!("Interrupt handler to dispatch tasks at priority {}", level);
|
let doc = format!("Interrupt handler to dispatch tasks at priority {}", level);
|
||||||
let interrupt = util::suffixed(&interrupts[&level].0.to_string());
|
|
||||||
let attribute = &interrupts[&level].1.attrs;
|
let attribute = &interrupts[&level].1.attrs;
|
||||||
items.push(quote!(
|
items.push(quote!(
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
|
|
|
@ -1,18 +1,15 @@
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use crate::syntax::{ast::App, Context};
|
||||||
use quote::quote;
|
|
||||||
use rtic_syntax::{ast::App, Context};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
analyze::Analysis,
|
analyze::Analysis,
|
||||||
check::Extra,
|
|
||||||
codegen::{local_resources_struct, module, shared_resources_struct},
|
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)
|
/// Generate support code for hardware tasks (`#[exception]`s and `#[interrupt]`s)
|
||||||
pub fn codegen(
|
pub fn codegen(
|
||||||
app: &App,
|
app: &App,
|
||||||
analysis: &Analysis,
|
analysis: &Analysis,
|
||||||
extra: &Extra,
|
|
||||||
) -> (
|
) -> (
|
||||||
// mod_app_hardware_tasks -- interrupt handlers and `${task}Resources` constructors
|
// mod_app_hardware_tasks -- interrupt handlers and `${task}Resources` constructors
|
||||||
Vec<TokenStream2>,
|
Vec<TokenStream2>,
|
||||||
|
@ -33,12 +30,10 @@ pub fn codegen(
|
||||||
let priority = task.args.priority;
|
let priority = task.args.priority;
|
||||||
let cfgs = &task.cfgs;
|
let cfgs = &task.cfgs;
|
||||||
let attrs = &task.attrs;
|
let attrs = &task.attrs;
|
||||||
let user_hardware_task_isr_doc = &format!(" User HW task ISR trampoline for {name}");
|
|
||||||
|
|
||||||
mod_app.push(quote!(
|
mod_app.push(quote!(
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
#[doc = #user_hardware_task_isr_doc]
|
|
||||||
#(#attrs)*
|
#(#attrs)*
|
||||||
#(#cfgs)*
|
#(#cfgs)*
|
||||||
unsafe fn #symbol() {
|
unsafe fn #symbol() {
|
||||||
|
@ -87,19 +82,14 @@ pub fn codegen(
|
||||||
local_needs_lt,
|
local_needs_lt,
|
||||||
app,
|
app,
|
||||||
analysis,
|
analysis,
|
||||||
extra,
|
|
||||||
));
|
));
|
||||||
|
|
||||||
let user_hardware_task_doc = &format!(" User HW task: {name}");
|
|
||||||
if !task.is_extern {
|
if !task.is_extern {
|
||||||
let attrs = &task.attrs;
|
let attrs = &task.attrs;
|
||||||
let cfgs = &task.cfgs;
|
|
||||||
let context = &task.context;
|
let context = &task.context;
|
||||||
let stmts = &task.stmts;
|
let stmts = &task.stmts;
|
||||||
user_tasks.push(quote!(
|
user_tasks.push(quote!(
|
||||||
#[doc = #user_hardware_task_doc]
|
|
||||||
#(#attrs)*
|
#(#attrs)*
|
||||||
#(#cfgs)*
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn #name(#context: #name::Context) {
|
fn #name(#context: #name::Context) {
|
||||||
use rtic::Mutex as _;
|
use rtic::Mutex as _;
|
||||||
|
|
|
@ -1,18 +1,15 @@
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use crate::syntax::{ast::App, Context};
|
||||||
use quote::quote;
|
|
||||||
use rtic_syntax::{ast::App, Context};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
analyze::Analysis,
|
analyze::Analysis,
|
||||||
check::Extra,
|
|
||||||
codegen::{local_resources_struct, module, shared_resources_struct},
|
codegen::{local_resources_struct, module, shared_resources_struct},
|
||||||
};
|
};
|
||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::quote;
|
||||||
|
|
||||||
/// Generates support code for `#[idle]` functions
|
/// Generates support code for `#[idle]` functions
|
||||||
pub fn codegen(
|
pub fn codegen(
|
||||||
app: &App,
|
app: &App,
|
||||||
analysis: &Analysis,
|
analysis: &Analysis,
|
||||||
extra: &Extra,
|
|
||||||
) -> (
|
) -> (
|
||||||
// mod_app_idle -- the `${idle}Resources` constructor
|
// mod_app_idle -- the `${idle}Resources` constructor
|
||||||
Vec<TokenStream2>,
|
Vec<TokenStream2>,
|
||||||
|
@ -57,16 +54,13 @@ pub fn codegen(
|
||||||
local_needs_lt,
|
local_needs_lt,
|
||||||
app,
|
app,
|
||||||
analysis,
|
analysis,
|
||||||
extra,
|
|
||||||
));
|
));
|
||||||
let idle_doc = " User provided idle function".to_string();
|
|
||||||
|
|
||||||
let attrs = &idle.attrs;
|
let attrs = &idle.attrs;
|
||||||
let context = &idle.context;
|
let context = &idle.context;
|
||||||
let stmts = &idle.stmts;
|
let stmts = &idle.stmts;
|
||||||
let user_idle = Some(quote!(
|
let user_idle = Some(quote!(
|
||||||
#(#attrs)*
|
#(#attrs)*
|
||||||
#[doc = #idle_doc]
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn #name(#context: #name::Context) -> ! {
|
fn #name(#context: #name::Context) -> ! {
|
||||||
use rtic::Mutex as _;
|
use rtic::Mutex as _;
|
||||||
|
@ -82,6 +76,9 @@ pub fn codegen(
|
||||||
|
|
||||||
(mod_app, root_idle, user_idle, call_idle)
|
(mod_app, root_idle, user_idle, call_idle)
|
||||||
} else {
|
} else {
|
||||||
|
// TODO: No idle defined, check for 0-priority tasks and generate an executor if needed
|
||||||
|
|
||||||
|
//
|
||||||
(
|
(
|
||||||
vec![],
|
vec![],
|
||||||
vec![],
|
vec![],
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use rtic_syntax::{ast::App, Context};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
analyze::Analysis,
|
analyze::Analysis,
|
||||||
check::Extra,
|
|
||||||
codegen::{local_resources_struct, module},
|
codegen::{local_resources_struct, module},
|
||||||
|
syntax::{ast::App, Context},
|
||||||
};
|
};
|
||||||
|
|
||||||
type CodegenResult = (
|
type CodegenResult = (
|
||||||
|
@ -24,7 +23,7 @@ type CodegenResult = (
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Generates support code for `#[init]` functions
|
/// Generates support code for `#[init]` functions
|
||||||
pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> CodegenResult {
|
pub fn codegen(app: &App, analysis: &Analysis) -> CodegenResult {
|
||||||
let init = &app.init;
|
let init = &app.init;
|
||||||
let mut local_needs_lt = false;
|
let mut local_needs_lt = false;
|
||||||
let name = &init.name;
|
let name = &init.name;
|
||||||
|
@ -65,27 +64,22 @@ pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> CodegenResult {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let shared_resources_doc = " RTIC shared resource struct".to_string();
|
|
||||||
let local_resources_doc = " RTIC local resource struct".to_string();
|
|
||||||
root_init.push(quote! {
|
root_init.push(quote! {
|
||||||
#[doc = #shared_resources_doc]
|
|
||||||
struct #shared {
|
struct #shared {
|
||||||
#(#shared_resources)*
|
#(#shared_resources)*
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc = #local_resources_doc]
|
|
||||||
struct #local {
|
struct #local {
|
||||||
#(#local_resources)*
|
#(#local_resources)*
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// let locals_pat = locals_pat.iter();
|
||||||
|
|
||||||
let user_init_return = quote! {#shared, #local, #name::Monotonics};
|
let user_init_return = quote! {#shared, #local, #name::Monotonics};
|
||||||
let user_init_doc = " User provided init function".to_string();
|
|
||||||
|
|
||||||
let user_init = quote!(
|
let user_init = quote!(
|
||||||
#(#attrs)*
|
#(#attrs)*
|
||||||
#[doc = #user_init_doc]
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn #name(#context: #name::Context) -> (#user_init_return) {
|
fn #name(#context: #name::Context) -> (#user_init_return) {
|
||||||
|
@ -105,6 +99,7 @@ pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> CodegenResult {
|
||||||
mod_app = Some(constructor);
|
mod_app = Some(constructor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// let locals_new = locals_new.iter();
|
||||||
let call_init = quote! {
|
let call_init = quote! {
|
||||||
let (shared_resources, local_resources, mut monotonics) = #name(#name::Context::new(core.into()));
|
let (shared_resources, local_resources, mut monotonics) = #name(#name::Context::new(core.into()));
|
||||||
};
|
};
|
||||||
|
@ -115,7 +110,6 @@ pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> CodegenResult {
|
||||||
local_needs_lt,
|
local_needs_lt,
|
||||||
app,
|
app,
|
||||||
analysis,
|
analysis,
|
||||||
extra,
|
|
||||||
));
|
));
|
||||||
|
|
||||||
(mod_app, root_init, user_init, call_init)
|
(mod_app, root_init, user_init, call_init)
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
|
use crate::syntax::ast::App;
|
||||||
|
use crate::{analyze::Analysis, codegen::util};
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use rtic_syntax::ast::App;
|
|
||||||
|
|
||||||
use crate::{analyze::Analysis, check::Extra, codegen::util};
|
|
||||||
|
|
||||||
/// Generates `local` variables and local resource proxies
|
/// Generates `local` variables and local resource proxies
|
||||||
///
|
///
|
||||||
|
@ -10,7 +9,6 @@ use crate::{analyze::Analysis, check::Extra, codegen::util};
|
||||||
pub fn codegen(
|
pub fn codegen(
|
||||||
app: &App,
|
app: &App,
|
||||||
_analysis: &Analysis,
|
_analysis: &Analysis,
|
||||||
_extra: &Extra,
|
|
||||||
) -> (
|
) -> (
|
||||||
// mod_app -- the `static` variables behind the proxies
|
// mod_app -- the `static` variables behind the proxies
|
||||||
Vec<TokenStream2>,
|
Vec<TokenStream2>,
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use crate::syntax::{
|
||||||
use quote::quote;
|
|
||||||
use rtic_syntax::{
|
|
||||||
ast::{App, TaskLocal},
|
ast::{App, TaskLocal},
|
||||||
Context,
|
Context,
|
||||||
};
|
};
|
||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::quote;
|
||||||
|
|
||||||
use crate::codegen::util;
|
use crate::codegen::util;
|
||||||
|
|
||||||
|
@ -13,7 +13,13 @@ pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2,
|
||||||
|
|
||||||
let resources = match ctxt {
|
let resources = match ctxt {
|
||||||
Context::Init => &app.init.args.local_resources,
|
Context::Init => &app.init.args.local_resources,
|
||||||
Context::Idle => &app.idle.as_ref().unwrap().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::HardwareTask(name) => &app.hardware_tasks[name].args.local_resources,
|
||||||
Context::SoftwareTask(name) => &app.software_tasks[name].args.local_resources,
|
Context::SoftwareTask(name) => &app.software_tasks[name].args.local_resources,
|
||||||
};
|
};
|
||||||
|
@ -49,9 +55,7 @@ pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2,
|
||||||
util::declared_static_local_resource_ident(name, &task_name)
|
util::declared_static_local_resource_ident(name, &task_name)
|
||||||
};
|
};
|
||||||
|
|
||||||
let local_resource_doc = format!(" Local resource `{name}`");
|
|
||||||
fields.push(quote!(
|
fields.push(quote!(
|
||||||
#[doc = #local_resource_doc]
|
|
||||||
#(#cfgs)*
|
#(#cfgs)*
|
||||||
pub #name: &#lt mut #ty
|
pub #name: &#lt mut #ty
|
||||||
));
|
));
|
||||||
|
@ -84,7 +88,7 @@ pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let doc = format!(" Local resources `{}` has access to", ctxt.ident(app));
|
let doc = format!("Local resources `{}` has access to", ctxt.ident(app));
|
||||||
let ident = util::local_resources_ident(ctxt, app);
|
let ident = util::local_resources_ident(ctxt, app);
|
||||||
let item = quote!(
|
let item = quote!(
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
|
@ -98,7 +102,6 @@ pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2,
|
||||||
let constructor = quote!(
|
let constructor = quote!(
|
||||||
impl<#lt> #ident<#lt> {
|
impl<#lt> #ident<#lt> {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[doc(hidden)]
|
|
||||||
pub unsafe fn new() -> Self {
|
pub unsafe fn new() -> Self {
|
||||||
#ident {
|
#ident {
|
||||||
#(#values,)*
|
#(#values,)*
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{analyze::Analysis, check::Extra, codegen::util};
|
use crate::syntax::{ast::App, Context};
|
||||||
|
use crate::{analyze::Analysis, codegen::util};
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use rtic_syntax::{ast::App, Context};
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
pub fn codegen(
|
pub fn codegen(
|
||||||
|
@ -10,12 +10,13 @@ pub fn codegen(
|
||||||
local_resources_tick: bool,
|
local_resources_tick: bool,
|
||||||
app: &App,
|
app: &App,
|
||||||
analysis: &Analysis,
|
analysis: &Analysis,
|
||||||
extra: &Extra,
|
|
||||||
) -> TokenStream2 {
|
) -> TokenStream2 {
|
||||||
let mut items = vec![];
|
let mut items = vec![];
|
||||||
let mut module_items = vec![];
|
let mut module_items = vec![];
|
||||||
let mut fields = vec![];
|
let mut fields = vec![];
|
||||||
let mut values = vec![];
|
let mut values = vec![];
|
||||||
|
// Used to copy task cfgs to the whole module
|
||||||
|
let mut task_cfgs = vec![];
|
||||||
|
|
||||||
let name = ctxt.ident(app);
|
let name = ctxt.ident(app);
|
||||||
|
|
||||||
|
@ -27,8 +28,8 @@ pub fn codegen(
|
||||||
pub core: rtic::export::Peripherals
|
pub core: rtic::export::Peripherals
|
||||||
));
|
));
|
||||||
|
|
||||||
if extra.peripherals {
|
if app.args.peripherals {
|
||||||
let device = &extra.device;
|
let device = &app.args.device;
|
||||||
|
|
||||||
fields.push(quote!(
|
fields.push(quote!(
|
||||||
/// Device peripherals
|
/// Device peripherals
|
||||||
|
@ -52,14 +53,6 @@ pub fn codegen(
|
||||||
Context::Idle | Context::HardwareTask(_) | Context::SoftwareTask(_) => {}
|
Context::Idle | Context::HardwareTask(_) | Context::SoftwareTask(_) => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if ctxt.has_locals(app) {
|
|
||||||
// let ident = util::locals_ident(ctxt, app);
|
|
||||||
// module_items.push(quote!(
|
|
||||||
// #[doc(inline)]
|
|
||||||
// pub use super::#ident as Locals;
|
|
||||||
// ));
|
|
||||||
// }
|
|
||||||
|
|
||||||
if ctxt.has_local_resources(app) {
|
if ctxt.has_local_resources(app) {
|
||||||
let ident = util::local_resources_ident(ctxt, app);
|
let ident = util::local_resources_ident(ctxt, app);
|
||||||
let lt = if local_resources_tick {
|
let lt = if local_resources_tick {
|
||||||
|
@ -114,12 +107,8 @@ pub fn codegen(
|
||||||
.monotonics
|
.monotonics
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(_, monotonic)| {
|
.map(|(_, monotonic)| {
|
||||||
let cfgs = &monotonic.cfgs;
|
|
||||||
let mono = &monotonic.ty;
|
let mono = &monotonic.ty;
|
||||||
quote! {
|
quote! {#mono}
|
||||||
#(#cfgs)*
|
|
||||||
pub #mono
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
@ -130,7 +119,7 @@ pub fn codegen(
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
pub struct #internal_monotonics_ident(
|
pub struct #internal_monotonics_ident(
|
||||||
#(#monotonic_types),*
|
#(pub #monotonic_types),*
|
||||||
);
|
);
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -141,10 +130,10 @@ pub fn codegen(
|
||||||
}
|
}
|
||||||
|
|
||||||
let doc = match ctxt {
|
let doc = match ctxt {
|
||||||
Context::Idle => " Idle loop",
|
Context::Idle => "Idle loop",
|
||||||
Context::Init => " Initialization function",
|
Context::Init => "Initialization function",
|
||||||
Context::HardwareTask(_) => " Hardware task",
|
Context::HardwareTask(_) => "Hardware task",
|
||||||
Context::SoftwareTask(_) => " Software task",
|
Context::SoftwareTask(_) => "Software task",
|
||||||
};
|
};
|
||||||
|
|
||||||
let v = Vec::new();
|
let v = Vec::new();
|
||||||
|
@ -175,8 +164,8 @@ pub fn codegen(
|
||||||
let internal_context_name = util::internal_task_ident(name, "Context");
|
let internal_context_name = util::internal_task_ident(name, "Context");
|
||||||
|
|
||||||
items.push(quote!(
|
items.push(quote!(
|
||||||
/// Execution context
|
|
||||||
#(#cfgs)*
|
#(#cfgs)*
|
||||||
|
/// Execution context
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
pub struct #internal_context_name<#lt> {
|
pub struct #internal_context_name<#lt> {
|
||||||
|
@ -185,7 +174,6 @@ pub fn codegen(
|
||||||
|
|
||||||
#(#cfgs)*
|
#(#cfgs)*
|
||||||
impl<#lt> #internal_context_name<#lt> {
|
impl<#lt> #internal_context_name<#lt> {
|
||||||
#[doc(hidden)]
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub unsafe fn new(#core #priority) -> Self {
|
pub unsafe fn new(#core #priority) -> Self {
|
||||||
#internal_context_name {
|
#internal_context_name {
|
||||||
|
@ -196,8 +184,8 @@ pub fn codegen(
|
||||||
));
|
));
|
||||||
|
|
||||||
module_items.push(quote!(
|
module_items.push(quote!(
|
||||||
#[doc(inline)]
|
|
||||||
#(#cfgs)*
|
#(#cfgs)*
|
||||||
|
#[doc(inline)]
|
||||||
pub use super::#internal_context_name as Context;
|
pub use super::#internal_context_name as Context;
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -206,6 +194,8 @@ pub fn codegen(
|
||||||
let priority = spawnee.args.priority;
|
let priority = spawnee.args.priority;
|
||||||
let t = util::spawn_t_ident(priority);
|
let t = util::spawn_t_ident(priority);
|
||||||
let cfgs = &spawnee.cfgs;
|
let cfgs = &spawnee.cfgs;
|
||||||
|
// Store a copy of the task cfgs
|
||||||
|
task_cfgs = cfgs.clone();
|
||||||
let (args, tupled, untupled, ty) = util::regroup_inputs(&spawnee.inputs);
|
let (args, tupled, untupled, ty) = util::regroup_inputs(&spawnee.inputs);
|
||||||
let args = &args;
|
let args = &args;
|
||||||
let tupled = &tupled;
|
let tupled = &tupled;
|
||||||
|
@ -213,21 +203,53 @@ pub fn codegen(
|
||||||
let rq = util::rq_ident(priority);
|
let rq = util::rq_ident(priority);
|
||||||
let inputs = util::inputs_ident(name);
|
let inputs = util::inputs_ident(name);
|
||||||
|
|
||||||
let device = &extra.device;
|
let device = &app.args.device;
|
||||||
let enum_ = util::interrupt_ident();
|
let enum_ = util::interrupt_ident();
|
||||||
let interrupt = &analysis
|
let interrupt = if spawnee.is_async {
|
||||||
.interrupts
|
&analysis
|
||||||
|
.interrupts_async
|
||||||
.get(&priority)
|
.get(&priority)
|
||||||
.expect("RTIC-ICE: interrupt identifer not found")
|
.expect("RTIC-ICE: interrupt identifer not found")
|
||||||
.0;
|
.0
|
||||||
|
} else {
|
||||||
|
&analysis
|
||||||
|
.interrupts_normal
|
||||||
|
.get(&priority)
|
||||||
|
.expect("RTIC-ICE: interrupt identifer not found")
|
||||||
|
.0
|
||||||
|
};
|
||||||
|
|
||||||
let internal_spawn_ident = util::internal_task_ident(name, "spawn");
|
let internal_spawn_ident = util::internal_task_ident(name, "spawn");
|
||||||
|
|
||||||
// Spawn caller
|
// Spawn caller
|
||||||
|
if spawnee.is_async {
|
||||||
|
let rq = util::rq_async_ident(name);
|
||||||
items.push(quote!(
|
items.push(quote!(
|
||||||
|
|
||||||
/// Spawns the task directly
|
|
||||||
#(#cfgs)*
|
#(#cfgs)*
|
||||||
|
/// Spawns the task directly
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn #internal_spawn_ident(#(#args,)*) -> Result<(), #ty> {
|
||||||
|
let input = #tupled;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let r = rtic::export::interrupt::free(|_| (&mut *#rq.get_mut()).enqueue(input));
|
||||||
|
|
||||||
|
if r.is_ok() {
|
||||||
|
rtic::pend(#device::#enum_::#interrupt);
|
||||||
|
}
|
||||||
|
|
||||||
|
r
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
items.push(quote!(
|
||||||
|
|
||||||
|
#(#cfgs)*
|
||||||
|
/// Spawns the task directly
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[doc(hidden)]
|
||||||
pub fn #internal_spawn_ident(#(#args,)*) -> Result<(), #ty> {
|
pub fn #internal_spawn_ident(#(#args,)*) -> Result<(), #ty> {
|
||||||
let input = #tupled;
|
let input = #tupled;
|
||||||
|
|
||||||
|
@ -242,7 +264,6 @@ pub fn codegen(
|
||||||
rtic::export::interrupt::free(|_| {
|
rtic::export::interrupt::free(|_| {
|
||||||
(&mut *#rq.get_mut()).enqueue_unchecked((#t::#name, index));
|
(&mut *#rq.get_mut()).enqueue_unchecked((#t::#name, index));
|
||||||
});
|
});
|
||||||
|
|
||||||
rtic::pend(#device::#enum_::#interrupt);
|
rtic::pend(#device::#enum_::#interrupt);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -252,14 +273,16 @@ pub fn codegen(
|
||||||
}
|
}
|
||||||
|
|
||||||
}));
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
module_items.push(quote!(
|
module_items.push(quote!(
|
||||||
#[doc(inline)]
|
|
||||||
#(#cfgs)*
|
#(#cfgs)*
|
||||||
|
#[doc(inline)]
|
||||||
pub use super::#internal_spawn_ident as spawn;
|
pub use super::#internal_spawn_ident as spawn;
|
||||||
));
|
));
|
||||||
|
|
||||||
// Schedule caller
|
// Schedule caller
|
||||||
|
if !spawnee.is_async {
|
||||||
for (_, monotonic) in &app.monotonics {
|
for (_, monotonic) in &app.monotonics {
|
||||||
let instants = util::monotonic_instants_ident(name, &monotonic.ident);
|
let instants = util::monotonic_instants_ident(name, &monotonic.ident);
|
||||||
let monotonic_name = monotonic.ident.to_string();
|
let monotonic_name = monotonic.ident.to_string();
|
||||||
|
@ -267,7 +290,6 @@ pub fn codegen(
|
||||||
let tq = util::tq_ident(&monotonic.ident.to_string());
|
let tq = util::tq_ident(&monotonic.ident.to_string());
|
||||||
let t = util::schedule_t_ident();
|
let t = util::schedule_t_ident();
|
||||||
let m = &monotonic.ident;
|
let m = &monotonic.ident;
|
||||||
let cfgs = &monotonic.cfgs;
|
|
||||||
let m_ident = util::monotonic_ident(&monotonic_name);
|
let m_ident = util::monotonic_ident(&monotonic_name);
|
||||||
let m_isr = &monotonic.args.binds;
|
let m_isr = &monotonic.args.binds;
|
||||||
let enum_ = util::interrupt_ident();
|
let enum_ = util::interrupt_ident();
|
||||||
|
@ -288,9 +310,6 @@ pub fn codegen(
|
||||||
|
|
||||||
let tq_marker = &util::timer_queue_marker_ident();
|
let tq_marker = &util::timer_queue_marker_ident();
|
||||||
|
|
||||||
// For future use
|
|
||||||
// let doc = format!(" RTIC internal: {}:{}", file!(), line!());
|
|
||||||
// items.push(quote!(#[doc = #doc]));
|
|
||||||
let internal_spawn_handle_ident =
|
let internal_spawn_handle_ident =
|
||||||
util::internal_monotonics_ident(name, m, "SpawnHandle");
|
util::internal_monotonics_ident(name, m, "SpawnHandle");
|
||||||
let internal_spawn_at_ident = util::internal_monotonics_ident(name, m, "spawn_at");
|
let internal_spawn_at_ident = util::internal_monotonics_ident(name, m, "spawn_at");
|
||||||
|
@ -299,26 +318,26 @@ pub fn codegen(
|
||||||
|
|
||||||
if monotonic.args.default {
|
if monotonic.args.default {
|
||||||
module_items.push(quote!(
|
module_items.push(quote!(
|
||||||
#(#cfgs)*
|
#[doc(inline)]
|
||||||
pub use #m::spawn_after;
|
pub use #m::spawn_after;
|
||||||
#(#cfgs)*
|
#[doc(inline)]
|
||||||
pub use #m::spawn_at;
|
pub use #m::spawn_at;
|
||||||
#(#cfgs)*
|
#[doc(inline)]
|
||||||
pub use #m::SpawnHandle;
|
pub use #m::SpawnHandle;
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
module_items.push(quote!(
|
module_items.push(quote!(
|
||||||
#[doc(hidden)]
|
|
||||||
#(#cfgs)*
|
|
||||||
pub mod #m {
|
pub mod #m {
|
||||||
|
#[doc(inline)]
|
||||||
pub use super::super::#internal_spawn_after_ident as spawn_after;
|
pub use super::super::#internal_spawn_after_ident as spawn_after;
|
||||||
|
#[doc(inline)]
|
||||||
pub use super::super::#internal_spawn_at_ident as spawn_at;
|
pub use super::super::#internal_spawn_at_ident as spawn_at;
|
||||||
|
#[doc(inline)]
|
||||||
pub use super::super::#internal_spawn_handle_ident as SpawnHandle;
|
pub use super::super::#internal_spawn_handle_ident as SpawnHandle;
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
|
||||||
items.push(quote!(
|
items.push(quote!(
|
||||||
#[doc(hidden)]
|
|
||||||
#(#cfgs)*
|
#(#cfgs)*
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
|
@ -329,7 +348,6 @@ pub fn codegen(
|
||||||
|
|
||||||
#(#cfgs)*
|
#(#cfgs)*
|
||||||
impl core::fmt::Debug for #internal_spawn_handle_ident {
|
impl core::fmt::Debug for #internal_spawn_handle_ident {
|
||||||
#[doc(hidden)]
|
|
||||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
f.debug_struct(#spawn_handle_string).finish()
|
f.debug_struct(#spawn_handle_string).finish()
|
||||||
}
|
}
|
||||||
|
@ -340,7 +358,7 @@ pub fn codegen(
|
||||||
pub fn cancel(self) -> Result<#ty, ()> {
|
pub fn cancel(self) -> Result<#ty, ()> {
|
||||||
rtic::export::interrupt::free(|_| unsafe {
|
rtic::export::interrupt::free(|_| unsafe {
|
||||||
let tq = &mut *#tq.get_mut();
|
let tq = &mut *#tq.get_mut();
|
||||||
if let Some((_task, index)) = tq.cancel_marker(self.marker) {
|
if let Some((_task, index)) = tq.cancel_task_marker(self.marker) {
|
||||||
// Get the message
|
// Get the message
|
||||||
let msg = (&*#inputs
|
let msg = (&*#inputs
|
||||||
.get())
|
.get())
|
||||||
|
@ -357,9 +375,7 @@ pub fn codegen(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reschedule after
|
|
||||||
#[inline]
|
#[inline]
|
||||||
#(#cfgs)*
|
|
||||||
pub fn reschedule_after(
|
pub fn reschedule_after(
|
||||||
self,
|
self,
|
||||||
duration: <#m as rtic::Monotonic>::Duration
|
duration: <#m as rtic::Monotonic>::Duration
|
||||||
|
@ -367,8 +383,6 @@ pub fn codegen(
|
||||||
self.reschedule_at(monotonics::#m::now() + duration)
|
self.reschedule_at(monotonics::#m::now() + duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reschedule at
|
|
||||||
#(#cfgs)*
|
|
||||||
pub fn reschedule_at(
|
pub fn reschedule_at(
|
||||||
self,
|
self,
|
||||||
instant: <#m as rtic::Monotonic>::Instant
|
instant: <#m as rtic::Monotonic>::Instant
|
||||||
|
@ -379,16 +393,17 @@ pub fn codegen(
|
||||||
|
|
||||||
let tq = (&mut *#tq.get_mut());
|
let tq = (&mut *#tq.get_mut());
|
||||||
|
|
||||||
tq.update_marker(self.marker, marker, instant, || #pend).map(|_| #name::#m::SpawnHandle { marker })
|
tq.update_task_marker(self.marker, marker, instant, || #pend).map(|_| #name::#m::SpawnHandle { marker })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#(#cfgs)*
|
||||||
/// Spawns the task after a set duration relative to the current time
|
/// Spawns the task after a set duration relative to the current time
|
||||||
///
|
///
|
||||||
/// This will use the time `Instant::new(0)` as baseline if called in `#[init]`,
|
/// This will use the time `Instant::new(0)` as baseline if called in `#[init]`,
|
||||||
/// so if you use a non-resetable timer use `spawn_at` when in `#[init]`
|
/// so if you use a non-resetable timer use `spawn_at` when in `#[init]`
|
||||||
#(#cfgs)*
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn #internal_spawn_after_ident(
|
pub fn #internal_spawn_after_ident(
|
||||||
duration: <#m as rtic::Monotonic>::Duration
|
duration: <#m as rtic::Monotonic>::Duration
|
||||||
|
@ -424,10 +439,10 @@ pub fn codegen(
|
||||||
|
|
||||||
rtic::export::interrupt::free(|_| {
|
rtic::export::interrupt::free(|_| {
|
||||||
let marker = #tq_marker.get().read();
|
let marker = #tq_marker.get().read();
|
||||||
let nr = rtic::export::NotReady {
|
let nr = rtic::export::TaskNotReady {
|
||||||
instant,
|
|
||||||
index,
|
|
||||||
task: #t::#name,
|
task: #t::#name,
|
||||||
|
index,
|
||||||
|
instant,
|
||||||
marker,
|
marker,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -435,7 +450,7 @@ pub fn codegen(
|
||||||
|
|
||||||
let tq = &mut *#tq.get_mut();
|
let tq = &mut *#tq.get_mut();
|
||||||
|
|
||||||
tq.enqueue_unchecked(
|
tq.enqueue_task_unchecked(
|
||||||
nr,
|
nr,
|
||||||
|| #enable_interrupt,
|
|| #enable_interrupt,
|
||||||
|| #pend,
|
|| #pend,
|
||||||
|
@ -451,14 +466,16 @@ pub fn codegen(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if items.is_empty() {
|
if items.is_empty() {
|
||||||
quote!()
|
quote!()
|
||||||
} else {
|
} else {
|
||||||
quote!(
|
quote!(
|
||||||
#(#items)*
|
#(#items)*
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#(#cfgs)*
|
#(#task_cfgs)*
|
||||||
#[doc = #doc]
|
#[doc = #doc]
|
||||||
pub mod #name {
|
pub mod #name {
|
||||||
#(#module_items)*
|
#(#module_items)*
|
||||||
|
|
280
macros/src/codegen/monotonic.rs
Normal file
280
macros/src/codegen/monotonic.rs
Normal file
|
@ -0,0 +1,280 @@
|
||||||
|
use crate::syntax::ast::App;
|
||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::quote;
|
||||||
|
|
||||||
|
use crate::{analyze::Analysis, codegen::util};
|
||||||
|
|
||||||
|
/// Generates monotonic module dispatchers
|
||||||
|
pub fn codegen(app: &App, _analysis: &Analysis) -> TokenStream2 {
|
||||||
|
let mut monotonic_parts: Vec<_> = Vec::new();
|
||||||
|
|
||||||
|
let tq_marker = util::timer_queue_marker_ident();
|
||||||
|
|
||||||
|
for (_, monotonic) in &app.monotonics {
|
||||||
|
// let instants = util::monotonic_instants_ident(name, &monotonic.ident);
|
||||||
|
let monotonic_name = monotonic.ident.to_string();
|
||||||
|
|
||||||
|
let tq = util::tq_ident(&monotonic_name);
|
||||||
|
let m = &monotonic.ident;
|
||||||
|
let m_ident = util::monotonic_ident(&monotonic_name);
|
||||||
|
let m_isr = &monotonic.args.binds;
|
||||||
|
let enum_ = util::interrupt_ident();
|
||||||
|
let name_str = &m.to_string();
|
||||||
|
let ident = util::monotonic_ident(name_str);
|
||||||
|
let doc = &format!(
|
||||||
|
"This module holds the static implementation for `{}::now()`",
|
||||||
|
name_str
|
||||||
|
);
|
||||||
|
|
||||||
|
let (enable_interrupt, pend) = if &*m_isr.to_string() == "SysTick" {
|
||||||
|
(
|
||||||
|
quote!(core::mem::transmute::<_, rtic::export::SYST>(()).enable_interrupt()),
|
||||||
|
quote!(rtic::export::SCB::set_pendst()),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
let rt_err = util::rt_err_ident();
|
||||||
|
(
|
||||||
|
quote!(rtic::export::NVIC::unmask(super::super::#rt_err::#enum_::#m_isr)),
|
||||||
|
quote!(rtic::pend(super::super::#rt_err::#enum_::#m_isr)),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let default_monotonic = if monotonic.args.default {
|
||||||
|
quote!(
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use #m::now;
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use #m::delay;
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use #m::delay_until;
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use #m::timeout_at;
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use #m::timeout_after;
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
quote!()
|
||||||
|
};
|
||||||
|
|
||||||
|
monotonic_parts.push(quote! {
|
||||||
|
#default_monotonic
|
||||||
|
|
||||||
|
#[doc = #doc]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub mod #m {
|
||||||
|
/// Read the current time from this monotonic
|
||||||
|
pub fn now() -> <super::super::#m as rtic::Monotonic>::Instant {
|
||||||
|
rtic::export::interrupt::free(|_| {
|
||||||
|
use rtic::Monotonic as _;
|
||||||
|
if let Some(m) = unsafe{ &mut *super::super::#ident.get_mut() } {
|
||||||
|
m.now()
|
||||||
|
} else {
|
||||||
|
<super::super::#m as rtic::Monotonic>::zero()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delay
|
||||||
|
#[inline(always)]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn delay(duration: <super::super::#m as rtic::Monotonic>::Duration)
|
||||||
|
-> DelayFuture {
|
||||||
|
let until = now() + duration;
|
||||||
|
DelayFuture { until, waker_storage: None }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delay until a specific time
|
||||||
|
#[inline(always)]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn delay_until(instant: <super::super::#m as rtic::Monotonic>::Instant)
|
||||||
|
-> DelayFuture {
|
||||||
|
let until = instant;
|
||||||
|
DelayFuture { until, waker_storage: None }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delay future.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
pub struct DelayFuture {
|
||||||
|
until: <super::super::#m as rtic::Monotonic>::Instant,
|
||||||
|
waker_storage: Option<rtic::export::IntrusiveNode<rtic::export::WakerNotReady<super::super::#m>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for DelayFuture {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some(waker_storage) = &mut self.waker_storage {
|
||||||
|
rtic::export::interrupt::free(|_| unsafe {
|
||||||
|
let tq = &mut *super::super::#tq.get_mut();
|
||||||
|
tq.cancel_waker_marker(waker_storage.val.marker);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::future::Future for DelayFuture {
|
||||||
|
type Output = ();
|
||||||
|
|
||||||
|
fn poll(
|
||||||
|
mut self: core::pin::Pin<&mut Self>,
|
||||||
|
cx: &mut core::task::Context<'_>
|
||||||
|
) -> core::task::Poll<Self::Output> {
|
||||||
|
let mut s = self.as_mut();
|
||||||
|
let now = now();
|
||||||
|
let until = s.until;
|
||||||
|
let is_ws_none = s.waker_storage.is_none();
|
||||||
|
|
||||||
|
if now >= until {
|
||||||
|
return core::task::Poll::Ready(());
|
||||||
|
} else if is_ws_none {
|
||||||
|
rtic::export::interrupt::free(|_| unsafe {
|
||||||
|
let marker = super::super::#tq_marker.get().read();
|
||||||
|
super::super::#tq_marker.get_mut().write(marker.wrapping_add(1));
|
||||||
|
|
||||||
|
let nr = s.waker_storage.insert(rtic::export::IntrusiveNode::new(rtic::export::WakerNotReady {
|
||||||
|
waker: cx.waker().clone(),
|
||||||
|
instant: until,
|
||||||
|
marker,
|
||||||
|
}));
|
||||||
|
|
||||||
|
let tq = &mut *super::super::#tq.get_mut();
|
||||||
|
|
||||||
|
tq.enqueue_waker(
|
||||||
|
core::mem::transmute(nr), // Transmute the reference to static
|
||||||
|
|| #enable_interrupt,
|
||||||
|
|| #pend,
|
||||||
|
(&mut *super::super::#m_ident.get_mut()).as_mut());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
core::task::Poll::Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Timeout future.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
pub struct TimeoutFuture<F: core::future::Future> {
|
||||||
|
future: F,
|
||||||
|
until: <super::super::#m as rtic::Monotonic>::Instant,
|
||||||
|
waker_storage: Option<rtic::export::IntrusiveNode<rtic::export::WakerNotReady<super::super::#m>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F: core::future::Future> Drop for TimeoutFuture<F> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some(waker_storage) = &mut self.waker_storage {
|
||||||
|
rtic::export::interrupt::free(|_| unsafe {
|
||||||
|
let tq = &mut *super::super::#tq.get_mut();
|
||||||
|
tq.cancel_waker_marker(waker_storage.val.marker);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Timeout after
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn timeout_after<F: core::future::Future>(
|
||||||
|
future: F,
|
||||||
|
duration: <super::super::#m as rtic::Monotonic>::Duration
|
||||||
|
) -> TimeoutFuture<F> {
|
||||||
|
let until = now() + duration;
|
||||||
|
TimeoutFuture {
|
||||||
|
future,
|
||||||
|
until,
|
||||||
|
waker_storage: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Timeout at
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn timeout_at<F: core::future::Future>(
|
||||||
|
future: F,
|
||||||
|
instant: <super::super::#m as rtic::Monotonic>::Instant
|
||||||
|
) -> TimeoutFuture<F> {
|
||||||
|
TimeoutFuture {
|
||||||
|
future,
|
||||||
|
until: instant,
|
||||||
|
waker_storage: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> core::future::Future for TimeoutFuture<F>
|
||||||
|
where
|
||||||
|
F: core::future::Future,
|
||||||
|
{
|
||||||
|
type Output = Result<F::Output, super::TimeoutError>;
|
||||||
|
|
||||||
|
fn poll(
|
||||||
|
self: core::pin::Pin<&mut Self>,
|
||||||
|
cx: &mut core::task::Context<'_>
|
||||||
|
) -> core::task::Poll<Self::Output> {
|
||||||
|
// SAFETY: We don't move the underlying pinned value.
|
||||||
|
let mut s = unsafe { self.get_unchecked_mut() };
|
||||||
|
let future = unsafe { core::pin::Pin::new_unchecked(&mut s.future) };
|
||||||
|
let now = now();
|
||||||
|
let until = s.until;
|
||||||
|
let is_ws_none = s.waker_storage.is_none();
|
||||||
|
|
||||||
|
match future.poll(cx) {
|
||||||
|
core::task::Poll::Ready(r) => {
|
||||||
|
if let Some(waker_storage) = &mut s.waker_storage {
|
||||||
|
rtic::export::interrupt::free(|_| unsafe {
|
||||||
|
let tq = &mut *super::super::#tq.get_mut();
|
||||||
|
tq.cancel_waker_marker(waker_storage.val.marker);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return core::task::Poll::Ready(Ok(r));
|
||||||
|
}
|
||||||
|
core::task::Poll::Pending => {
|
||||||
|
if now >= until {
|
||||||
|
// Timeout
|
||||||
|
return core::task::Poll::Ready(Err(super::TimeoutError));
|
||||||
|
} else if is_ws_none {
|
||||||
|
rtic::export::interrupt::free(|_| unsafe {
|
||||||
|
let marker = super::super::#tq_marker.get().read();
|
||||||
|
super::super::#tq_marker.get_mut().write(marker.wrapping_add(1));
|
||||||
|
|
||||||
|
let nr = s.waker_storage.insert(rtic::export::IntrusiveNode::new(rtic::export::WakerNotReady {
|
||||||
|
waker: cx.waker().clone(),
|
||||||
|
instant: until,
|
||||||
|
marker,
|
||||||
|
}));
|
||||||
|
|
||||||
|
let tq = &mut *super::super::#tq.get_mut();
|
||||||
|
|
||||||
|
tq.enqueue_waker(
|
||||||
|
core::mem::transmute(nr), // Transmute the reference to static
|
||||||
|
|| #enable_interrupt,
|
||||||
|
|| #pend,
|
||||||
|
(&mut *super::super::#m_ident.get_mut()).as_mut());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
core::task::Poll::Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if monotonic_parts.is_empty() {
|
||||||
|
quote!()
|
||||||
|
} else {
|
||||||
|
quote!(
|
||||||
|
pub use rtic::Monotonic as _;
|
||||||
|
|
||||||
|
/// Holds static methods for each monotonic.
|
||||||
|
pub mod monotonics {
|
||||||
|
/// A timeout error.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TimeoutError;
|
||||||
|
|
||||||
|
#(#monotonic_parts)*
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
|
use crate::syntax::ast::App;
|
||||||
use proc_macro2::{Span, TokenStream as TokenStream2};
|
use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use rtic_syntax::ast::App;
|
|
||||||
use syn::Index;
|
use syn::Index;
|
||||||
|
|
||||||
use crate::{analyze::Analysis, codegen::util};
|
use crate::{analyze::Analysis, codegen::util};
|
||||||
|
@ -43,28 +43,21 @@ pub fn codegen(app: &App, analysis: &Analysis) -> Vec<TokenStream2> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i, (monotonic_ident, monotonic)) in app.monotonics.iter().enumerate() {
|
for (i, (monotonic, _)) in app.monotonics.iter().enumerate() {
|
||||||
// For future use
|
// For future use
|
||||||
// let doc = format!(" RTIC internal: {}:{}", file!(), line!());
|
// let doc = format!(" RTIC internal: {}:{}", file!(), line!());
|
||||||
// stmts.push(quote!(#[doc = #doc]));
|
// stmts.push(quote!(#[doc = #doc]));
|
||||||
let cfgs = &monotonic.cfgs;
|
|
||||||
|
|
||||||
#[allow(clippy::cast_possible_truncation)]
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
let idx = Index {
|
let idx = Index {
|
||||||
index: i as u32,
|
index: i as u32,
|
||||||
span: Span::call_site(),
|
span: Span::call_site(),
|
||||||
};
|
};
|
||||||
stmts.push(quote!(
|
stmts.push(quote!(monotonics.#idx.reset();));
|
||||||
#(#cfgs)*
|
|
||||||
monotonics.#idx.reset();
|
|
||||||
));
|
|
||||||
|
|
||||||
// Store the monotonic
|
// Store the monotonic
|
||||||
let name = util::monotonic_ident(&monotonic_ident.to_string());
|
let name = util::monotonic_ident(&monotonic.to_string());
|
||||||
stmts.push(quote!(
|
stmts.push(quote!(#name.get_mut().write(Some(monotonics.#idx));));
|
||||||
#(#cfgs)*
|
|
||||||
#name.get_mut().write(Some(monotonics.#idx));
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable the interrupts -- this completes the `init`-ialization phase
|
// Enable the interrupts -- this completes the `init`-ialization phase
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
|
use crate::syntax::ast::App;
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use rtic_syntax::ast::App;
|
|
||||||
|
|
||||||
use crate::{analyze::Analysis, check::Extra, codegen::util};
|
use crate::{analyze::Analysis, codegen::util};
|
||||||
|
|
||||||
/// Generates code that runs before `#[init]`
|
/// Generates code that runs before `#[init]`
|
||||||
pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec<TokenStream2> {
|
pub fn codegen(app: &App, analysis: &Analysis) -> Vec<TokenStream2> {
|
||||||
let mut stmts = vec![];
|
let mut stmts = vec![];
|
||||||
|
|
||||||
let rt_err = util::rt_err_ident();
|
let rt_err = util::rt_err_ident();
|
||||||
|
@ -15,12 +15,14 @@ pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec<TokenStream
|
||||||
|
|
||||||
// Populate the FreeQueue
|
// Populate the FreeQueue
|
||||||
for (name, task) in &app.software_tasks {
|
for (name, task) in &app.software_tasks {
|
||||||
|
if task.is_async {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let cap = task.args.capacity;
|
let cap = task.args.capacity;
|
||||||
let cfgs = &task.cfgs;
|
|
||||||
let fq_ident = util::fq_ident(name);
|
let fq_ident = util::fq_ident(name);
|
||||||
|
|
||||||
stmts.push(quote!(
|
stmts.push(quote!(
|
||||||
#(#cfgs)*
|
|
||||||
(0..#cap).for_each(|i| (&mut *#fq_ident.get_mut()).enqueue_unchecked(i));
|
(0..#cap).for_each(|i| (&mut *#fq_ident.get_mut()).enqueue_unchecked(i));
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -30,17 +32,21 @@ pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec<TokenStream
|
||||||
let mut core: rtic::export::Peripherals = rtic::export::Peripherals::steal().into();
|
let mut core: rtic::export::Peripherals = rtic::export::Peripherals::steal().into();
|
||||||
));
|
));
|
||||||
|
|
||||||
let device = &extra.device;
|
let device = &app.args.device;
|
||||||
let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS);
|
let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS);
|
||||||
|
|
||||||
// check that all dispatchers exists in the `Interrupt` enumeration regardless of whether
|
// check that all dispatchers exists in the `Interrupt` enumeration regardless of whether
|
||||||
// they are used or not
|
// they are used or not
|
||||||
let interrupt = util::interrupt_ident();
|
let interrupt = util::interrupt_ident();
|
||||||
for name in app.args.extern_interrupts.keys() {
|
for name in app.args.dispatchers.keys() {
|
||||||
stmts.push(quote!(let _ = #rt_err::#interrupt::#name;));
|
stmts.push(quote!(let _ = #rt_err::#interrupt::#name;));
|
||||||
}
|
}
|
||||||
|
|
||||||
let interrupt_ids = analysis.interrupts.iter().map(|(p, (id, _))| (p, id));
|
let interrupt_ids = analysis
|
||||||
|
.interrupts_normal
|
||||||
|
.iter()
|
||||||
|
.map(|(p, (id, _))| (p, id))
|
||||||
|
.chain(analysis.interrupts_async.iter().map(|(p, (id, _))| (p, id)));
|
||||||
|
|
||||||
// Unmask interrupts and set their priorities
|
// Unmask interrupts and set their priorities
|
||||||
for (&priority, name) in interrupt_ids.chain(app.hardware_tasks.values().filter_map(|task| {
|
for (&priority, name) in interrupt_ids.chain(app.hardware_tasks.values().filter_map(|task| {
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
use crate::{analyze::Analysis, check::Extra, codegen::util};
|
use crate::syntax::{analyze::Ownership, ast::App};
|
||||||
|
use crate::{analyze::Analysis, codegen::util};
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use rtic_syntax::{analyze::Ownership, ast::App};
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
/// Generates `static` variables and shared resource proxies
|
/// Generates `static` variables and shared resource proxies
|
||||||
pub fn codegen(
|
pub fn codegen(
|
||||||
app: &App,
|
app: &App,
|
||||||
analysis: &Analysis,
|
analysis: &Analysis,
|
||||||
extra: &Extra,
|
|
||||||
) -> (
|
) -> (
|
||||||
// mod_app -- the `static` variables behind the proxies
|
// mod_app -- the `static` variables behind the proxies
|
||||||
Vec<TokenStream2>,
|
Vec<TokenStream2>,
|
||||||
|
@ -90,7 +89,7 @@ pub fn codegen(
|
||||||
// let doc = format!(" RTIC internal ({} resource): {}:{}", doc, file!(), line!());
|
// let doc = format!(" RTIC internal ({} resource): {}:{}", doc, file!(), line!());
|
||||||
|
|
||||||
mod_app.push(util::impl_mutex(
|
mod_app.push(util::impl_mutex(
|
||||||
extra,
|
app,
|
||||||
cfgs,
|
cfgs,
|
||||||
true,
|
true,
|
||||||
&shared_name,
|
&shared_name,
|
||||||
|
@ -112,10 +111,14 @@ pub fn codegen(
|
||||||
};
|
};
|
||||||
|
|
||||||
// Computing mapping of used interrupts to masks
|
// Computing mapping of used interrupts to masks
|
||||||
let interrupt_ids = analysis.interrupts.iter().map(|(p, (id, _))| (p, id));
|
let interrupt_ids = analysis
|
||||||
|
.interrupts_normal
|
||||||
|
.iter()
|
||||||
|
.map(|(p, (id, _))| (p, id))
|
||||||
|
.chain(analysis.interrupts_async.iter().map(|(p, (id, _))| (p, id)));
|
||||||
|
|
||||||
let mut prio_to_masks = HashMap::new();
|
let mut prio_to_masks = HashMap::new();
|
||||||
let device = &extra.device;
|
let device = &app.args.device;
|
||||||
let mut uses_exceptions_with_resources = false;
|
let mut uses_exceptions_with_resources = false;
|
||||||
|
|
||||||
let mut mask_ids = Vec::new();
|
let mut mask_ids = Vec::new();
|
||||||
|
@ -147,8 +150,7 @@ pub fn codegen(
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})) {
|
})) {
|
||||||
#[allow(clippy::or_fun_call)]
|
let v: &mut Vec<_> = prio_to_masks.entry(priority - 1).or_default();
|
||||||
let v = prio_to_masks.entry(priority - 1).or_insert(Vec::new());
|
|
||||||
v.push(quote!(#device::Interrupt::#name as u32));
|
v.push(quote!(#device::Interrupt::#name as u32));
|
||||||
mask_ids.push(quote!(#device::Interrupt::#name as u32));
|
mask_ids.push(quote!(#device::Interrupt::#name as u32));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
use crate::syntax::{ast::App, Context};
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use rtic_syntax::{ast::App, Context};
|
|
||||||
|
|
||||||
use crate::codegen::util;
|
use crate::codegen::util;
|
||||||
|
|
||||||
|
@ -10,24 +10,17 @@ pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2,
|
||||||
|
|
||||||
let resources = match ctxt {
|
let resources = match ctxt {
|
||||||
Context::Init => unreachable!("Tried to generate shared resources struct for init"),
|
Context::Init => unreachable!("Tried to generate shared resources struct for init"),
|
||||||
Context::Idle => &app.idle.as_ref().unwrap().args.shared_resources,
|
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::HardwareTask(name) => &app.hardware_tasks[name].args.shared_resources,
|
||||||
Context::SoftwareTask(name) => &app.software_tasks[name].args.shared_resources,
|
Context::SoftwareTask(name) => &app.software_tasks[name].args.shared_resources,
|
||||||
};
|
};
|
||||||
|
|
||||||
let v = Vec::new();
|
|
||||||
let task_cfgs = match ctxt {
|
|
||||||
Context::HardwareTask(t) => {
|
|
||||||
&app.hardware_tasks[t].cfgs
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
Context::SoftwareTask(t) => {
|
|
||||||
&app.software_tasks[t].cfgs
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
_ => &v,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut fields = vec![];
|
let mut fields = vec![];
|
||||||
let mut values = vec![];
|
let mut values = vec![];
|
||||||
let mut has_cfgs = false;
|
let mut has_cfgs = false;
|
||||||
|
@ -57,18 +50,14 @@ pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2,
|
||||||
quote!('a)
|
quote!('a)
|
||||||
};
|
};
|
||||||
|
|
||||||
let lock_free_resource_doc = format!(" Lock free resource `{name}`");
|
|
||||||
fields.push(quote!(
|
fields.push(quote!(
|
||||||
#[doc = #lock_free_resource_doc]
|
|
||||||
#(#cfgs)*
|
#(#cfgs)*
|
||||||
pub #name: &#lt #mut_ #ty
|
pub #name: &#lt #mut_ #ty
|
||||||
));
|
));
|
||||||
} else if access.is_shared() {
|
} else if access.is_shared() {
|
||||||
lt = Some(quote!('a));
|
lt = Some(quote!('a));
|
||||||
|
|
||||||
let shared_resource_doc = format!(" Shared resource `{name}`");
|
|
||||||
fields.push(quote!(
|
fields.push(quote!(
|
||||||
#[doc = #shared_resource_doc]
|
|
||||||
#(#cfgs)*
|
#(#cfgs)*
|
||||||
pub #name: &'a #ty
|
pub #name: &'a #ty
|
||||||
));
|
));
|
||||||
|
@ -76,16 +65,12 @@ pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2,
|
||||||
// Resource proxy
|
// Resource proxy
|
||||||
lt = Some(quote!('a));
|
lt = Some(quote!('a));
|
||||||
|
|
||||||
let resource_doc =
|
|
||||||
format!(" Resource proxy resource `{name}`. Use method `.lock()` to gain access");
|
|
||||||
fields.push(quote!(
|
fields.push(quote!(
|
||||||
#[doc = #resource_doc]
|
|
||||||
#(#cfgs)*
|
#(#cfgs)*
|
||||||
pub #name: shared_resources::#shared_name<'a>
|
pub #name: shared_resources::#shared_name<'a>
|
||||||
));
|
));
|
||||||
|
|
||||||
values.push(quote!(
|
values.push(quote!(
|
||||||
#[doc(hidden)]
|
|
||||||
#(#cfgs)*
|
#(#cfgs)*
|
||||||
#name: shared_resources::#shared_name::new(priority)
|
#name: shared_resources::#shared_name::new(priority)
|
||||||
|
|
||||||
|
@ -95,17 +80,13 @@ pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2,
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let resource_doc;
|
|
||||||
let expr = if access.is_exclusive() {
|
let expr = if access.is_exclusive() {
|
||||||
resource_doc = format!(" Exclusive access resource `{name}`");
|
|
||||||
quote!(&mut *(&mut *#mangled_name.get_mut()).as_mut_ptr())
|
quote!(&mut *(&mut *#mangled_name.get_mut()).as_mut_ptr())
|
||||||
} else {
|
} else {
|
||||||
resource_doc = format!(" Non-exclusive access resource `{name}`");
|
|
||||||
quote!(&*(&*#mangled_name.get()).as_ptr())
|
quote!(&*(&*#mangled_name.get()).as_ptr())
|
||||||
};
|
};
|
||||||
|
|
||||||
values.push(quote!(
|
values.push(quote!(
|
||||||
#[doc = #resource_doc]
|
|
||||||
#(#cfgs)*
|
#(#cfgs)*
|
||||||
#name: #expr
|
#name: #expr
|
||||||
));
|
));
|
||||||
|
@ -125,13 +106,12 @@ pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let doc = format!(" Shared resources `{}` has access to", ctxt.ident(app));
|
let doc = format!("Shared resources `{}` has access to", ctxt.ident(app));
|
||||||
let ident = util::shared_resources_ident(ctxt, app);
|
let ident = util::shared_resources_ident(ctxt, app);
|
||||||
let item = quote!(
|
let item = quote!(
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
#[doc = #doc]
|
#[doc = #doc]
|
||||||
#(#task_cfgs)*
|
|
||||||
pub struct #ident<#lt> {
|
pub struct #ident<#lt> {
|
||||||
#(#fields,)*
|
#(#fields,)*
|
||||||
}
|
}
|
||||||
|
@ -143,9 +123,7 @@ pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2,
|
||||||
Some(quote!(priority: &#lt rtic::export::Priority))
|
Some(quote!(priority: &#lt rtic::export::Priority))
|
||||||
};
|
};
|
||||||
let constructor = quote!(
|
let constructor = quote!(
|
||||||
#(#task_cfgs)*
|
|
||||||
impl<#lt> #ident<#lt> {
|
impl<#lt> #ident<#lt> {
|
||||||
#[doc(hidden)]
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub unsafe fn new(#arg) -> Self {
|
pub unsafe fn new(#arg) -> Self {
|
||||||
#ident {
|
#ident {
|
||||||
|
|
|
@ -1,17 +1,14 @@
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use crate::syntax::{ast::App, Context};
|
||||||
use quote::quote;
|
|
||||||
use rtic_syntax::{ast::App, Context};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
analyze::Analysis,
|
analyze::Analysis,
|
||||||
check::Extra,
|
|
||||||
codegen::{local_resources_struct, module, shared_resources_struct, util},
|
codegen::{local_resources_struct, module, shared_resources_struct, util},
|
||||||
};
|
};
|
||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::quote;
|
||||||
|
|
||||||
pub fn codegen(
|
pub fn codegen(
|
||||||
app: &App,
|
app: &App,
|
||||||
analysis: &Analysis,
|
analysis: &Analysis,
|
||||||
extra: &Extra,
|
|
||||||
) -> (
|
) -> (
|
||||||
// mod_app_software_tasks -- free queues, buffers and `${task}Resources` constructors
|
// mod_app_software_tasks -- free queues, buffers and `${task}Resources` constructors
|
||||||
Vec<TokenStream2>,
|
Vec<TokenStream2>,
|
||||||
|
@ -27,15 +24,16 @@ pub fn codegen(
|
||||||
let mut root = vec![];
|
let mut root = vec![];
|
||||||
let mut user_tasks = vec![];
|
let mut user_tasks = vec![];
|
||||||
|
|
||||||
for (name, task) in &app.software_tasks {
|
// Any task
|
||||||
|
for (name, task) in app.software_tasks.iter() {
|
||||||
let inputs = &task.inputs;
|
let inputs = &task.inputs;
|
||||||
let cfgs = &task.cfgs;
|
|
||||||
let (_, _, _, input_ty) = util::regroup_inputs(inputs);
|
let (_, _, _, input_ty) = util::regroup_inputs(inputs);
|
||||||
|
|
||||||
let cap = task.args.capacity;
|
let cap = task.args.capacity;
|
||||||
let cap_lit = util::capacity_literal(cap as usize);
|
let cap_lit = util::capacity_literal(cap as usize);
|
||||||
let cap_lit_p1 = util::capacity_literal(cap as usize + 1);
|
let cap_lit_p1 = util::capacity_literal(cap as usize + 1);
|
||||||
|
|
||||||
|
if !task.is_async {
|
||||||
// Create free queues and inputs / instants buffers
|
// Create free queues and inputs / instants buffers
|
||||||
let fq = util::fq_ident(name);
|
let fq = util::fq_ident(name);
|
||||||
|
|
||||||
|
@ -47,10 +45,10 @@ pub fn codegen(
|
||||||
Box::new(|| Some(util::link_section_uninit())),
|
Box::new(|| Some(util::link_section_uninit())),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
mod_app.push(quote!(
|
mod_app.push(quote!(
|
||||||
// /// Queue version of a free-list that keeps track of empty slots in
|
// /// Queue version of a free-list that keeps track of empty slots in
|
||||||
// /// the following buffers
|
// /// the following buffers
|
||||||
#(#cfgs)*
|
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
#[allow(non_upper_case_globals)]
|
#[allow(non_upper_case_globals)]
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
|
@ -64,7 +62,6 @@ pub fn codegen(
|
||||||
for (_, monotonic) in &app.monotonics {
|
for (_, monotonic) in &app.monotonics {
|
||||||
let instants = util::monotonic_instants_ident(name, &monotonic.ident);
|
let instants = util::monotonic_instants_ident(name, &monotonic.ident);
|
||||||
let mono_type = &monotonic.ty;
|
let mono_type = &monotonic.ty;
|
||||||
let cfgs = &monotonic.cfgs;
|
|
||||||
|
|
||||||
let uninit = mk_uninit();
|
let uninit = mk_uninit();
|
||||||
// For future use
|
// For future use
|
||||||
|
@ -76,7 +73,6 @@ pub fn codegen(
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
#[allow(non_upper_case_globals)]
|
#[allow(non_upper_case_globals)]
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#(#cfgs)*
|
|
||||||
static #instants:
|
static #instants:
|
||||||
rtic::RacyCell<[core::mem::MaybeUninit<<#mono_type as rtic::Monotonic>::Instant>; #cap_lit]> =
|
rtic::RacyCell<[core::mem::MaybeUninit<<#mono_type as rtic::Monotonic>::Instant>; #cap_lit]> =
|
||||||
rtic::RacyCell::new([#(#elems,)*]);
|
rtic::RacyCell::new([#(#elems,)*]);
|
||||||
|
@ -85,16 +81,30 @@ pub fn codegen(
|
||||||
|
|
||||||
let uninit = mk_uninit();
|
let uninit = mk_uninit();
|
||||||
let inputs_ident = util::inputs_ident(name);
|
let inputs_ident = util::inputs_ident(name);
|
||||||
|
|
||||||
|
// Buffer that holds the inputs of a task
|
||||||
mod_app.push(quote!(
|
mod_app.push(quote!(
|
||||||
#uninit
|
#uninit
|
||||||
// /// Buffer that holds the inputs of a task
|
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
#[allow(non_upper_case_globals)]
|
#[allow(non_upper_case_globals)]
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#(#cfgs)*
|
|
||||||
static #inputs_ident: rtic::RacyCell<[core::mem::MaybeUninit<#input_ty>; #cap_lit]> =
|
static #inputs_ident: rtic::RacyCell<[core::mem::MaybeUninit<#input_ty>; #cap_lit]> =
|
||||||
rtic::RacyCell::new([#(#elems,)*]);
|
rtic::RacyCell::new([#(#elems,)*]);
|
||||||
));
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if task.is_async {
|
||||||
|
let executor_ident = util::executor_run_ident(name);
|
||||||
|
mod_app.push(quote!(
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
|
#[doc(hidden)]
|
||||||
|
static #executor_ident: core::sync::atomic::AtomicBool =
|
||||||
|
core::sync::atomic::AtomicBool::new(false);
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let inputs = &task.inputs;
|
||||||
|
|
||||||
// `${task}Resources`
|
// `${task}Resources`
|
||||||
let mut shared_needs_lt = false;
|
let mut shared_needs_lt = false;
|
||||||
|
@ -130,13 +140,24 @@ pub fn codegen(
|
||||||
let attrs = &task.attrs;
|
let attrs = &task.attrs;
|
||||||
let cfgs = &task.cfgs;
|
let cfgs = &task.cfgs;
|
||||||
let stmts = &task.stmts;
|
let stmts = &task.stmts;
|
||||||
let user_task_doc = format!(" User SW task {name}");
|
let (async_marker, context_lifetime) = if task.is_async {
|
||||||
|
(
|
||||||
|
quote!(async),
|
||||||
|
if shared_needs_lt || local_needs_lt {
|
||||||
|
quote!(<'static>)
|
||||||
|
} else {
|
||||||
|
quote!()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(quote!(), quote!())
|
||||||
|
};
|
||||||
|
|
||||||
user_tasks.push(quote!(
|
user_tasks.push(quote!(
|
||||||
#[doc = #user_task_doc]
|
|
||||||
#(#attrs)*
|
#(#attrs)*
|
||||||
#(#cfgs)*
|
#(#cfgs)*
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn #name(#context: #name::Context #(,#inputs)*) {
|
#async_marker fn #name(#context: #name::Context #context_lifetime #(,#inputs)*) {
|
||||||
use rtic::Mutex as _;
|
use rtic::Mutex as _;
|
||||||
use rtic::mutex::prelude::*;
|
use rtic::mutex::prelude::*;
|
||||||
|
|
||||||
|
@ -151,7 +172,6 @@ pub fn codegen(
|
||||||
local_needs_lt,
|
local_needs_lt,
|
||||||
app,
|
app,
|
||||||
analysis,
|
analysis,
|
||||||
extra,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
|
use crate::syntax::ast::App;
|
||||||
|
use crate::{analyze::Analysis, codegen::util};
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use rtic_syntax::ast::App;
|
|
||||||
|
|
||||||
use crate::{analyze::Analysis, check::Extra, codegen::util};
|
|
||||||
|
|
||||||
/// Generates timer queues and timer queue handlers
|
/// Generates timer queues and timer queue handlers
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec<TokenStream2> {
|
pub fn codegen(app: &App, analysis: &Analysis) -> Vec<TokenStream2> {
|
||||||
let mut items = vec![];
|
let mut items = vec![];
|
||||||
|
|
||||||
if !app.monotonics.is_empty() {
|
if !app.monotonics.is_empty() {
|
||||||
// Generate the marker counter used to track for `cancel` and `reschedule`
|
// Generate the marker counter used to track for `cancel` and `reschedule`
|
||||||
let tq_marker = util::timer_queue_marker_ident();
|
let tq_marker = util::timer_queue_marker_ident();
|
||||||
items.push(quote!(
|
items.push(quote!(
|
||||||
|
// #[doc = #doc]
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
#[allow(non_upper_case_globals)]
|
#[allow(non_upper_case_globals)]
|
||||||
|
@ -26,6 +26,7 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec<TokenStrea
|
||||||
let variants = app
|
let variants = app
|
||||||
.software_tasks
|
.software_tasks
|
||||||
.iter()
|
.iter()
|
||||||
|
.filter(|(_, task)| !task.is_async)
|
||||||
.map(|(name, task)| {
|
.map(|(name, task)| {
|
||||||
let cfgs = &task.cfgs;
|
let cfgs = &task.cfgs;
|
||||||
|
|
||||||
|
@ -55,7 +56,6 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec<TokenStrea
|
||||||
let tq = util::tq_ident(&monotonic_name);
|
let tq = util::tq_ident(&monotonic_name);
|
||||||
let t = util::schedule_t_ident();
|
let t = util::schedule_t_ident();
|
||||||
let mono_type = &monotonic.ty;
|
let mono_type = &monotonic.ty;
|
||||||
let cfgs = &monotonic.cfgs;
|
|
||||||
let m_ident = util::monotonic_ident(&monotonic_name);
|
let m_ident = util::monotonic_ident(&monotonic_name);
|
||||||
|
|
||||||
// Static variables and resource proxy
|
// Static variables and resource proxy
|
||||||
|
@ -67,8 +67,8 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec<TokenStrea
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(_name, task)| task.args.capacity as usize)
|
.map(|(_name, task)| task.args.capacity as usize)
|
||||||
.sum();
|
.sum();
|
||||||
let n = util::capacity_literal(cap);
|
let n_task = util::capacity_literal(cap);
|
||||||
let tq_ty = quote!(rtic::export::TimerQueue<#mono_type, #t, #n>);
|
let tq_ty = quote!(rtic::export::TimerQueue<#mono_type, #t, #n_task>);
|
||||||
|
|
||||||
// For future use
|
// For future use
|
||||||
// let doc = format!(" RTIC internal: {}:{}", file!(), line!());
|
// let doc = format!(" RTIC internal: {}:{}", file!(), line!());
|
||||||
|
@ -76,9 +76,12 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec<TokenStrea
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
#[allow(non_upper_case_globals)]
|
#[allow(non_upper_case_globals)]
|
||||||
#(#cfgs)*
|
static #tq: rtic::RacyCell<#tq_ty> = rtic::RacyCell::new(
|
||||||
static #tq: rtic::RacyCell<#tq_ty> =
|
rtic::export::TimerQueue {
|
||||||
rtic::RacyCell::new(rtic::export::TimerQueue(rtic::export::SortedLinkedList::new_u16()));
|
task_queue: rtic::export::SortedLinkedList::new_u16(),
|
||||||
|
waker_queue: rtic::export::IntrusiveSortedLinkedList::new(),
|
||||||
|
}
|
||||||
|
);
|
||||||
));
|
));
|
||||||
|
|
||||||
let mono = util::monotonic_ident(&monotonic_name);
|
let mono = util::monotonic_ident(&monotonic_name);
|
||||||
|
@ -89,7 +92,6 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec<TokenStrea
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
#[allow(non_upper_case_globals)]
|
#[allow(non_upper_case_globals)]
|
||||||
#(#cfgs)*
|
|
||||||
static #mono: rtic::RacyCell<Option<#mono_type>> = rtic::RacyCell::new(None);
|
static #mono: rtic::RacyCell<Option<#mono_type>> = rtic::RacyCell::new(None);
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -102,6 +104,7 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec<TokenStrea
|
||||||
let arms = app
|
let arms = app
|
||||||
.software_tasks
|
.software_tasks
|
||||||
.iter()
|
.iter()
|
||||||
|
.filter(|(_, task)| !task.is_async)
|
||||||
.map(|(name, task)| {
|
.map(|(name, task)| {
|
||||||
let cfgs = &task.cfgs;
|
let cfgs = &task.cfgs;
|
||||||
let priority = task.args.priority;
|
let priority = task.args.priority;
|
||||||
|
@ -109,7 +112,7 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec<TokenStrea
|
||||||
let rqt = util::spawn_t_ident(priority);
|
let rqt = util::spawn_t_ident(priority);
|
||||||
|
|
||||||
// The interrupt that runs the task dispatcher
|
// The interrupt that runs the task dispatcher
|
||||||
let interrupt = &analysis.interrupts.get(&priority).expect("RTIC-ICE: interrupt not found").0;
|
let interrupt = &analysis.interrupts_normal.get(&priority).expect("RTIC-ICE: interrupt not found").0;
|
||||||
|
|
||||||
let pend = {
|
let pend = {
|
||||||
quote!(
|
quote!(
|
||||||
|
@ -120,7 +123,9 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec<TokenStrea
|
||||||
quote!(
|
quote!(
|
||||||
#(#cfgs)*
|
#(#cfgs)*
|
||||||
#t::#name => {
|
#t::#name => {
|
||||||
rtic::export::interrupt::free(|_| (&mut *#rq.get_mut()).split().0.enqueue_unchecked((#rqt::#name, index)));
|
rtic::export::interrupt::free(|_|
|
||||||
|
(&mut *#rq.get_mut()).split().0.enqueue_unchecked((#rqt::#name, index))
|
||||||
|
);
|
||||||
|
|
||||||
#pend
|
#pend
|
||||||
}
|
}
|
||||||
|
@ -128,7 +133,6 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec<TokenStrea
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let cfgs = &monotonic.cfgs;
|
|
||||||
let bound_interrupt = &monotonic.args.binds;
|
let bound_interrupt = &monotonic.args.binds;
|
||||||
let disable_isr = if &*bound_interrupt.to_string() == "SysTick" {
|
let disable_isr = if &*bound_interrupt.to_string() == "SysTick" {
|
||||||
quote!(core::mem::transmute::<_, rtic::export::SYST>(()).disable_interrupt())
|
quote!(core::mem::transmute::<_, rtic::export::SYST>(()).disable_interrupt())
|
||||||
|
@ -139,7 +143,6 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec<TokenStrea
|
||||||
items.push(quote!(
|
items.push(quote!(
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#(#cfgs)*
|
|
||||||
unsafe fn #bound_interrupt() {
|
unsafe fn #bound_interrupt() {
|
||||||
while let Some((task, index)) = rtic::export::interrupt::free(|_|
|
while let Some((task, index)) = rtic::export::interrupt::free(|_|
|
||||||
if let Some(mono) = (&mut *#m_ident.get_mut()).as_mut() {
|
if let Some(mono) = (&mut *#m_ident.get_mut()).as_mut() {
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
use core::sync::atomic::{AtomicUsize, Ordering};
|
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
|
use crate::syntax::{ast::App, Context};
|
||||||
use proc_macro2::{Span, TokenStream as TokenStream2};
|
use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use rtic_syntax::{ast::App, Context};
|
|
||||||
use syn::{Attribute, Ident, LitInt, PatType};
|
use syn::{Attribute, Ident, LitInt, PatType};
|
||||||
|
|
||||||
use crate::check::Extra;
|
|
||||||
|
|
||||||
const RTIC_INTERNAL: &str = "__rtic_internal";
|
const RTIC_INTERNAL: &str = "__rtic_internal";
|
||||||
|
|
||||||
/// Turns `capacity` into an unsuffixed integer literal
|
/// Turns `capacity` into an unsuffixed integer literal
|
||||||
|
@ -21,7 +19,7 @@ pub fn fq_ident(task: &Ident) -> Ident {
|
||||||
|
|
||||||
/// Generates a `Mutex` implementation
|
/// Generates a `Mutex` implementation
|
||||||
pub fn impl_mutex(
|
pub fn impl_mutex(
|
||||||
extra: &Extra,
|
app: &App,
|
||||||
cfgs: &[Attribute],
|
cfgs: &[Attribute],
|
||||||
resources_prefix: bool,
|
resources_prefix: bool,
|
||||||
name: &Ident,
|
name: &Ident,
|
||||||
|
@ -35,7 +33,7 @@ pub fn impl_mutex(
|
||||||
(quote!(#name), quote!(self.priority))
|
(quote!(#name), quote!(self.priority))
|
||||||
};
|
};
|
||||||
|
|
||||||
let device = &extra.device;
|
let device = &app.args.device;
|
||||||
let masks_name = priority_masks_ident();
|
let masks_name = priority_masks_ident();
|
||||||
quote!(
|
quote!(
|
||||||
#(#cfgs)*
|
#(#cfgs)*
|
||||||
|
@ -67,6 +65,11 @@ pub fn inputs_ident(task: &Ident) -> Ident {
|
||||||
mark_internal_name(&format!("{}_INPUTS", task))
|
mark_internal_name(&format!("{}_INPUTS", task))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generates an identifier for the `EXECUTOR_RUN` atomics (`async` API)
|
||||||
|
pub fn executor_run_ident(task: &Ident) -> Ident {
|
||||||
|
mark_internal_name(&format!("{}_EXECUTOR_RUN", task))
|
||||||
|
}
|
||||||
|
|
||||||
/// Generates an identifier for the `INSTANTS` buffer (`schedule` API)
|
/// Generates an identifier for the `INSTANTS` buffer (`schedule` API)
|
||||||
pub fn monotonic_instants_ident(task: &Ident, monotonic: &Ident) -> Ident {
|
pub fn monotonic_instants_ident(task: &Ident, monotonic: &Ident) -> Ident {
|
||||||
mark_internal_name(&format!("{}_{}_INSTANTS", task, monotonic))
|
mark_internal_name(&format!("{}_{}_INSTANTS", task, monotonic))
|
||||||
|
@ -179,7 +182,12 @@ pub fn regroup_inputs(
|
||||||
pub fn get_task_name(ctxt: Context, app: &App) -> Ident {
|
pub fn get_task_name(ctxt: Context, app: &App) -> Ident {
|
||||||
let s = match ctxt {
|
let s = match ctxt {
|
||||||
Context::Init => app.init.name.to_string(),
|
Context::Init => app.init.name.to_string(),
|
||||||
Context::Idle => app.idle.as_ref().unwrap().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(),
|
Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -190,7 +198,12 @@ pub fn get_task_name(ctxt: Context, app: &App) -> Ident {
|
||||||
pub fn shared_resources_ident(ctxt: Context, app: &App) -> Ident {
|
pub fn shared_resources_ident(ctxt: Context, app: &App) -> Ident {
|
||||||
let mut s = match ctxt {
|
let mut s = match ctxt {
|
||||||
Context::Init => app.init.name.to_string(),
|
Context::Init => app.init.name.to_string(),
|
||||||
Context::Idle => app.idle.as_ref().unwrap().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(),
|
Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -203,7 +216,12 @@ pub fn shared_resources_ident(ctxt: Context, app: &App) -> Ident {
|
||||||
pub fn local_resources_ident(ctxt: Context, app: &App) -> Ident {
|
pub fn local_resources_ident(ctxt: Context, app: &App) -> Ident {
|
||||||
let mut s = match ctxt {
|
let mut s = match ctxt {
|
||||||
Context::Init => app.init.name.to_string(),
|
Context::Init => app.init.name.to_string(),
|
||||||
Context::Idle => app.idle.as_ref().unwrap().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(),
|
Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -220,9 +238,14 @@ pub fn rq_ident(priority: u8) -> Ident {
|
||||||
mark_internal_name(&format!("P{}_RQ", priority))
|
mark_internal_name(&format!("P{}_RQ", priority))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generates an identifier for a ready queue, async task version
|
||||||
|
pub fn rq_async_ident(async_task_name: &Ident) -> Ident {
|
||||||
|
mark_internal_name(&format!("ASYNC_TACK_{}_RQ", async_task_name))
|
||||||
|
}
|
||||||
|
|
||||||
/// Generates an identifier for the `enum` of `schedule`-able tasks
|
/// Generates an identifier for the `enum` of `schedule`-able tasks
|
||||||
pub fn schedule_t_ident() -> Ident {
|
pub fn schedule_t_ident() -> Ident {
|
||||||
Ident::new("SCHED_T", Span::call_site())
|
mark_internal_name("SCHED_T")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates an identifier for the `enum` of `spawn`-able tasks
|
/// Generates an identifier for the `enum` of `spawn`-able tasks
|
||||||
|
@ -230,7 +253,7 @@ pub fn schedule_t_ident() -> Ident {
|
||||||
/// This identifier needs the same structure as the `RQ` identifier because there's one ready queue
|
/// This identifier needs the same structure as the `RQ` identifier because there's one ready queue
|
||||||
/// for each of these `T` enums
|
/// for each of these `T` enums
|
||||||
pub fn spawn_t_ident(priority: u8) -> Ident {
|
pub fn spawn_t_ident(priority: u8) -> Ident {
|
||||||
Ident::new(&format!("P{}_T", priority), Span::call_site())
|
mark_internal_name(&format!("P{}_T", priority))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Suffixed identifier
|
/// Suffixed identifier
|
||||||
|
|
|
@ -1,21 +1,46 @@
|
||||||
#![doc(
|
#![doc(
|
||||||
html_logo_url = "https://raw.githubusercontent.com/rtic-rs/cortex-m-rtic/master/book/en/src/RTIC.svg",
|
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/cortex-m-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
|
|
||||||
|
|
||||||
extern crate proc_macro;
|
//deny_warnings_placeholder_for_ci
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use std::{env, fs, path::Path};
|
use std::{env, fs, path::Path};
|
||||||
|
|
||||||
use rtic_syntax::Settings;
|
|
||||||
|
|
||||||
mod analyze;
|
mod analyze;
|
||||||
mod check;
|
mod bindings;
|
||||||
mod codegen;
|
mod codegen;
|
||||||
#[cfg(test)]
|
mod syntax;
|
||||||
mod tests;
|
|
||||||
|
// Used for mocking the API in testing
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn mock_app(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
|
let mut settings = syntax::Settings::default();
|
||||||
|
let mut rtic_args = vec![];
|
||||||
|
for arg in args.to_string().split(',') {
|
||||||
|
if arg.trim() == "parse_binds" {
|
||||||
|
settings.parse_binds = true;
|
||||||
|
} else if arg.trim() == "parse_extern_interrupt" {
|
||||||
|
settings.parse_extern_interrupt = true;
|
||||||
|
} else {
|
||||||
|
rtic_args.push(arg.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// rtic_args.push("device = mock".into());
|
||||||
|
|
||||||
|
let args = rtic_args.join(", ").parse();
|
||||||
|
|
||||||
|
println!("args: {:?}", args);
|
||||||
|
|
||||||
|
if let Err(e) = syntax::parse(args.unwrap(), input, settings) {
|
||||||
|
e.to_compile_error().into()
|
||||||
|
} else {
|
||||||
|
"fn main() {}".parse().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Attribute used to declare a RTIC application
|
/// Attribute used to declare a RTIC application
|
||||||
///
|
///
|
||||||
|
@ -26,24 +51,19 @@ mod tests;
|
||||||
/// Should never panic, cargo feeds a path which is later converted to a string
|
/// Should never panic, cargo feeds a path which is later converted to a string
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn app(args: TokenStream, input: TokenStream) -> TokenStream {
|
pub fn app(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
let mut settings = Settings::default();
|
let mut settings = syntax::Settings::default();
|
||||||
settings.optimize_priorities = false;
|
settings.optimize_priorities = false;
|
||||||
settings.parse_binds = true;
|
settings.parse_binds = true;
|
||||||
settings.parse_extern_interrupt = true;
|
settings.parse_extern_interrupt = true;
|
||||||
|
|
||||||
let (app, analysis) = match rtic_syntax::parse(args, input, settings) {
|
let (app, analysis) = match syntax::parse(args, input, settings) {
|
||||||
Err(e) => return e.to_compile_error().into(),
|
|
||||||
Ok(x) => x,
|
|
||||||
};
|
|
||||||
|
|
||||||
let extra = match check::app(&app, &analysis) {
|
|
||||||
Err(e) => return e.to_compile_error().into(),
|
Err(e) => return e.to_compile_error().into(),
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
};
|
};
|
||||||
|
|
||||||
let analysis = analyze::app(analysis, &app);
|
let analysis = analyze::app(analysis, &app);
|
||||||
|
|
||||||
let ts = codegen::app(&app, &analysis, &extra);
|
let ts = codegen::app(&app, &analysis);
|
||||||
|
|
||||||
// Default output path: <project_dir>/target/
|
// Default output path: <project_dir>/target/
|
||||||
let mut out_dir = Path::new("target");
|
let mut out_dir = Path::new("target");
|
||||||
|
@ -52,22 +72,7 @@ pub fn app(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
// TODO don't want to break builds if OUT_DIR is not set, is this ever the case?
|
// 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());
|
let out_str = env::var("OUT_DIR").unwrap_or_else(|_| "".to_string());
|
||||||
|
|
||||||
// Assuming we are building for a thumbv* target
|
if !out_dir.exists() {
|
||||||
let target_triple_prefix = "thumbv";
|
|
||||||
|
|
||||||
// Check for special scenario where default target/ directory is not present
|
|
||||||
//
|
|
||||||
// This is configurable in .cargo/config:
|
|
||||||
//
|
|
||||||
// [build]
|
|
||||||
// target-dir = "target"
|
|
||||||
#[cfg(feature = "debugprint")]
|
|
||||||
println!("OUT_DIR\n{:#?}", out_str);
|
|
||||||
|
|
||||||
if out_dir.exists() {
|
|
||||||
#[cfg(feature = "debugprint")]
|
|
||||||
println!("\ntarget/ exists\n");
|
|
||||||
} else {
|
|
||||||
// Set out_dir to OUT_DIR
|
// Set out_dir to OUT_DIR
|
||||||
out_dir = Path::new(&out_str);
|
out_dir = Path::new(&out_str);
|
||||||
|
|
||||||
|
@ -81,16 +86,11 @@ pub fn app(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
// If no "target" directory is found, <project_dir>/<out_dir_root> is used
|
// If no "target" directory is found, <project_dir>/<out_dir_root> is used
|
||||||
for path in out_dir.ancestors() {
|
for path in out_dir.ancestors() {
|
||||||
if let Some(dir) = path.components().last() {
|
if let Some(dir) = path.components().last() {
|
||||||
if dir
|
let dir = dir.as_os_str().to_str().unwrap();
|
||||||
.as_os_str()
|
|
||||||
.to_str()
|
if dir.starts_with("thumbv") || dir.starts_with("riscv") {
|
||||||
.unwrap()
|
|
||||||
.starts_with(target_triple_prefix)
|
|
||||||
{
|
|
||||||
if let Some(out) = path.parent() {
|
if let Some(out) = path.parent() {
|
||||||
out_dir = out;
|
out_dir = out;
|
||||||
#[cfg(feature = "debugprint")]
|
|
||||||
println!("{:#?}\n", out_dir);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// If no parent, just use it
|
// If no parent, just use it
|
||||||
|
@ -103,8 +103,6 @@ pub fn app(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
|
|
||||||
// Try to write the expanded code to disk
|
// Try to write the expanded code to disk
|
||||||
if let Some(out_str) = out_dir.to_str() {
|
if let Some(out_str) = out_dir.to_str() {
|
||||||
#[cfg(feature = "debugprint")]
|
|
||||||
println!("Write file:\n{}/rtic-expansion.rs\n", out_str);
|
|
||||||
fs::write(format!("{}/rtic-expansion.rs", out_str), ts.to_string()).ok();
|
fs::write(format!("{}/rtic-expansion.rs", out_str), ts.to_string()).ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
158
macros/src/syntax.rs
Normal file
158
macros/src/syntax.rs
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
#[allow(unused_extern_crates)]
|
||||||
|
extern crate proc_macro;
|
||||||
|
|
||||||
|
use core::ops;
|
||||||
|
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 optimize;
|
||||||
|
mod parse;
|
||||||
|
|
||||||
|
/// An ordered map keyed by identifier
|
||||||
|
pub type Map<T> = IndexMap<Ident, T>;
|
||||||
|
|
||||||
|
/// An order set
|
||||||
|
pub type Set<T> = IndexSet<T>;
|
||||||
|
|
||||||
|
/// Immutable pointer
|
||||||
|
pub struct P<T> {
|
||||||
|
ptr: Box<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> P<T> {
|
||||||
|
/// Boxes `x` making the value immutable
|
||||||
|
pub fn new(x: T) -> P<T> {
|
||||||
|
P { ptr: Box::new(x) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> ops::Deref for P<T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &T {
|
||||||
|
&self.ptr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execution context
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum Context<'a> {
|
||||||
|
/// The `idle` context
|
||||||
|
Idle,
|
||||||
|
|
||||||
|
/// The `init`-ialization function
|
||||||
|
Init,
|
||||||
|
|
||||||
|
/// A software task: `#[task]`
|
||||||
|
SoftwareTask(&'a Ident),
|
||||||
|
|
||||||
|
/// A hardware task: `#[exception]` or `#[interrupt]`
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parser and optimizer configuration
|
||||||
|
#[derive(Default)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct Settings {
|
||||||
|
/// Whether to accept the `binds` argument in `#[task]` or not
|
||||||
|
pub parse_binds: bool,
|
||||||
|
/// Whether to parse `extern` interrupts (functions) or not
|
||||||
|
pub parse_extern_interrupt: bool,
|
||||||
|
/// Whether to "compress" priorities or not
|
||||||
|
pub optimize_priorities: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses the input of the `#[app]` attribute
|
||||||
|
pub fn parse(
|
||||||
|
args: TokenStream,
|
||||||
|
input: TokenStream,
|
||||||
|
settings: Settings,
|
||||||
|
) -> Result<(ast::App, analyze::Analysis), syn::parse::Error> {
|
||||||
|
parse2(args.into(), input.into(), settings)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `proc_macro2::TokenStream` version of `parse`
|
||||||
|
pub fn parse2(
|
||||||
|
args: TokenStream2,
|
||||||
|
input: TokenStream2,
|
||||||
|
settings: Settings,
|
||||||
|
) -> Result<(ast::App, analyze::Analysis), syn::parse::Error> {
|
||||||
|
let mut app = parse::app(args, input, &settings)?;
|
||||||
|
check::app(&app)?;
|
||||||
|
optimize::app(&mut app, &settings);
|
||||||
|
|
||||||
|
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),
|
||||||
|
}
|
3
macros/src/syntax/.github/bors.toml
vendored
Normal file
3
macros/src/syntax/.github/bors.toml
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
block_labels = ["S-blocked"]
|
||||||
|
delete_merged_branches = true
|
||||||
|
status = ["ci"]
|
213
macros/src/syntax/.github/workflows/build.yml
vendored
Normal file
213
macros/src/syntax/.github/workflows/build.yml
vendored
Normal file
|
@ -0,0 +1,213 @@
|
||||||
|
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
|
28
macros/src/syntax/.github/workflows/changelog.yml
vendored
Normal file
28
macros/src/syntax/.github/workflows/changelog.yml
vendored
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
# 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 }}
|
6
macros/src/syntax/.github/workflows/properties/build.properties.json
vendored
Normal file
6
macros/src/syntax/.github/workflows/properties/build.properties.json
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"name": "Build",
|
||||||
|
"description": "RTIC Test Suite",
|
||||||
|
"iconName": "rust",
|
||||||
|
"categories": ["Rust"]
|
||||||
|
}
|
4
macros/src/syntax/.gitignore
vendored
Normal file
4
macros/src/syntax/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
**/*.rs.bk
|
||||||
|
.#*
|
||||||
|
/target/
|
||||||
|
Cargo.lock
|
31
macros/src/syntax/.travis.yml
Normal file
31
macros/src/syntax/.travis.yml
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
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
|
113
macros/src/syntax/accessors.rs
Normal file
113
macros/src/syntax/accessors.rs
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
448
macros/src/syntax/analyze.rs
Normal file
448
macros/src/syntax/analyze.rs
Normal file
|
@ -0,0 +1,448 @@
|
||||||
|
//! 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 = String;
|
||||||
|
type Priority = u8;
|
||||||
|
|
||||||
|
// The task list is a Tuple (Name, Shared Resources, Local Resources, Priority, IsAsync)
|
||||||
|
let task_resources_list: Vec<(TaskName, Vec<&Ident>, &LocalResources, Priority, bool)> =
|
||||||
|
Some(&app.init)
|
||||||
|
.iter()
|
||||||
|
.map(|ht| {
|
||||||
|
(
|
||||||
|
"init".to_string(),
|
||||||
|
Vec::new(),
|
||||||
|
&ht.args.local_resources,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.chain(app.idle.iter().map(|ht| {
|
||||||
|
(
|
||||||
|
"idle".to_string(),
|
||||||
|
ht.args
|
||||||
|
.shared_resources
|
||||||
|
.iter()
|
||||||
|
.map(|(v, _)| v)
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
&ht.args.local_resources,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
.chain(app.software_tasks.iter().map(|(name, ht)| {
|
||||||
|
(
|
||||||
|
name.to_string(),
|
||||||
|
ht.args
|
||||||
|
.shared_resources
|
||||||
|
.iter()
|
||||||
|
.map(|(v, _)| v)
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
&ht.args.local_resources,
|
||||||
|
ht.args.priority,
|
||||||
|
ht.is_async,
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
.chain(app.hardware_tasks.iter().map(|(name, ht)| {
|
||||||
|
(
|
||||||
|
name.to_string(),
|
||||||
|
ht.args
|
||||||
|
.shared_resources
|
||||||
|
.iter()
|
||||||
|
.map(|(v, _)| v)
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
&ht.args.local_resources,
|
||||||
|
ht.args.priority,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
.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, is_async) in task_resources_list.iter() {
|
||||||
|
for r in tr {
|
||||||
|
// Get all uses of resources annotated lock_free
|
||||||
|
if lf_res == r {
|
||||||
|
// lock_free resources are not allowed in async tasks
|
||||||
|
if *is_async {
|
||||||
|
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!(
|
||||||
|
"Software task {:?} has priority 0, but `#[idle]` is defined. 0-priority software tasks are only allowed if there is no `#[idle]`.",
|
||||||
|
name.to_string(),
|
||||||
|
)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0-priority tasks must be async
|
||||||
|
if !task.is_async {
|
||||||
|
error.push(syn::Error::new(
|
||||||
|
name.span(),
|
||||||
|
format!(
|
||||||
|
"Software task {:?} has priority 0, but is not `async`. 0-priority software tasks must be `async`.",
|
||||||
|
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`
|
||||||
|
let mut send_types = SendTypes::new();
|
||||||
|
let owned_by_idle = Ownership::Owned { priority: 0 };
|
||||||
|
for (name, res) in app.shared_resources.iter() {
|
||||||
|
// Handle not owned by idle
|
||||||
|
if ownerships
|
||||||
|
.get(name)
|
||||||
|
.map(|ownership| *ownership != owned_by_idle)
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
send_types.insert(res.ty.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Most local resources need to be `Send` as well
|
||||||
|
for (name, res) in app.local_resources.iter() {
|
||||||
|
if let Some(idle) = &app.idle {
|
||||||
|
// Only Send if not in idle or not at idle prio
|
||||||
|
if idle.args.local_resources.get(name).is_none()
|
||||||
|
&& !ownerships
|
||||||
|
.get(name)
|
||||||
|
.map(|ownership| *ownership != owned_by_idle)
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
send_types.insert(res.ty.clone());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
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());
|
||||||
|
|
||||||
|
if !spawnee.args.only_same_priority_spawn {
|
||||||
|
// Require `Send` if the task can be spawned from other priorities
|
||||||
|
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()));
|
||||||
|
|
||||||
|
// Compute channel capacities
|
||||||
|
for channel in channels.values_mut() {
|
||||||
|
channel.capacity = channel
|
||||||
|
.tasks
|
||||||
|
.iter()
|
||||||
|
.map(|name| app.software_tasks[name].args.capacity)
|
||||||
|
.sum();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 { .. })
|
||||||
|
}
|
||||||
|
}
|
380
macros/src/syntax/ast.rs
Normal file
380
macros/src/syntax/ast.rs
Normal file
|
@ -0,0 +1,380 @@
|
||||||
|
//! 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>,
|
||||||
|
|
||||||
|
/// Monotonic clocks
|
||||||
|
pub monotonics: Map<Monotonic>,
|
||||||
|
|
||||||
|
/// 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>,
|
||||||
|
|
||||||
|
/// 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>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Monotonic
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct Monotonic {
|
||||||
|
/// `#[cfg]` attributes like `#[cfg(debug_assertions)]`
|
||||||
|
pub cfgs: Vec<Attribute>,
|
||||||
|
|
||||||
|
/// The identifier of the monotonic
|
||||||
|
pub ident: Ident,
|
||||||
|
|
||||||
|
/// The type of this monotonic
|
||||||
|
pub ty: Box<Type>,
|
||||||
|
|
||||||
|
/// Monotonic args
|
||||||
|
pub args: MonotonicArgs,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Monotonic metadata
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct MonotonicArgs {
|
||||||
|
/// The interrupt or exception that this monotonic is bound to
|
||||||
|
pub binds: Ident,
|
||||||
|
|
||||||
|
/// The priority of this monotonic
|
||||||
|
pub priority: Option<u8>,
|
||||||
|
|
||||||
|
/// If this is the default monotonic
|
||||||
|
pub default: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A 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,
|
||||||
|
|
||||||
|
/// If the task is marked as `async`
|
||||||
|
pub is_async: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Software task metadata
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct SoftwareTaskArgs {
|
||||||
|
/// The task capacity: the maximum number of pending messages that can be queued
|
||||||
|
pub capacity: u8,
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
|
||||||
|
/// Only same priority tasks can spawn this task
|
||||||
|
pub only_same_priority_spawn: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SoftwareTaskArgs {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
capacity: 1,
|
||||||
|
priority: 1,
|
||||||
|
local_resources: LocalResources::new(),
|
||||||
|
shared_resources: SharedResources::new(),
|
||||||
|
only_same_priority_spawn: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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>;
|
66
macros/src/syntax/check.rs
Normal file
66
macros/src/syntax/check.rs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
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(())
|
||||||
|
}
|
36
macros/src/syntax/optimize.rs
Normal file
36
macros/src/syntax/optimize.rs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
use std::collections::{BTreeSet, HashMap};
|
||||||
|
|
||||||
|
use crate::syntax::{ast::App, Settings};
|
||||||
|
|
||||||
|
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)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
520
macros/src/syntax/parse.rs
Normal file
520
macros/src/syntax/parse.rs
Normal file
|
@ -0,0 +1,520 @@
|
||||||
|
mod app;
|
||||||
|
mod hardware_task;
|
||||||
|
mod idle;
|
||||||
|
mod init;
|
||||||
|
mod monotonic;
|
||||||
|
mod resource;
|
||||||
|
mod software_task;
|
||||||
|
mod util;
|
||||||
|
|
||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use syn::{
|
||||||
|
braced, parenthesized,
|
||||||
|
parse::{self, Parse, ParseStream, Parser},
|
||||||
|
token::{self, Brace},
|
||||||
|
Ident, Item, LitBool, LitInt, Path, Token,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::syntax::{
|
||||||
|
ast::{
|
||||||
|
App, AppArgs, HardwareTaskArgs, IdleArgs, InitArgs, MonotonicArgs, SoftwareTaskArgs,
|
||||||
|
TaskLocal,
|
||||||
|
},
|
||||||
|
Either, Settings,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parse the app, both app arguments and body (input)
|
||||||
|
pub fn app(args: TokenStream2, input: TokenStream2, settings: &Settings) -> parse::Result<App> {
|
||||||
|
let args = AppArgs::parse(args)?;
|
||||||
|
let input: Input = syn::parse2(input)?;
|
||||||
|
|
||||||
|
App::parse(args, input, settings)
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
settings: &Settings,
|
||||||
|
) -> 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 mut only_same_priority_spawn = false;
|
||||||
|
let mut only_same_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();
|
||||||
|
|
||||||
|
if ident_s == "only_same_priority_spawn_please_fix_me" {
|
||||||
|
if only_same_priority_spawn {
|
||||||
|
return Err(parse::Error::new(
|
||||||
|
ident.span(),
|
||||||
|
"argument appears more than once",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
only_same_priority_spawn = true;
|
||||||
|
only_same_prio_span = Some(ident.span());
|
||||||
|
|
||||||
|
if content.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle comma: ,
|
||||||
|
let _: Token![,] = content.parse()?;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle equal sign
|
||||||
|
let _: Token![=] = content.parse()?;
|
||||||
|
|
||||||
|
match &*ident_s {
|
||||||
|
"binds" if !settings.parse_binds => {
|
||||||
|
return Err(parse::Error::new(
|
||||||
|
ident.span(),
|
||||||
|
"Unexpected bind in task argument. Binds are only parsed if Settings::parse_binds is set.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
"binds" if settings.parse_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",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if only_same_priority_spawn {
|
||||||
|
return Err(parse::Error::new(
|
||||||
|
only_same_prio_span.unwrap(),
|
||||||
|
"hardware tasks are not allowed to be spawned, `only_same_priority_spawn_please_fix_me` is only for software tasks",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Either::Left(HardwareTaskArgs {
|
||||||
|
binds,
|
||||||
|
priority,
|
||||||
|
shared_resources,
|
||||||
|
local_resources,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Either::Right(SoftwareTaskArgs {
|
||||||
|
capacity: capacity.unwrap_or(1),
|
||||||
|
priority,
|
||||||
|
shared_resources,
|
||||||
|
local_resources,
|
||||||
|
only_same_priority_spawn,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.parse2(tokens)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn monotonic_args(path: Path, tokens: TokenStream2) -> parse::Result<MonotonicArgs> {
|
||||||
|
(|input: ParseStream<'_>| -> parse::Result<MonotonicArgs> {
|
||||||
|
let mut binds = None;
|
||||||
|
let mut priority = None;
|
||||||
|
let mut default = None;
|
||||||
|
|
||||||
|
if !input.peek(token::Paren) {
|
||||||
|
return Err(parse::Error::new(
|
||||||
|
path.segments.first().unwrap().ident.span(),
|
||||||
|
"expected opening ( in #[monotonic( ... )]",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
"binds" => {
|
||||||
|
if binds.is_some() {
|
||||||
|
return Err(parse::Error::new(
|
||||||
|
ident.span(),
|
||||||
|
"argument appears more than once",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
// Parse identifier name
|
||||||
|
let ident = content.parse()?;
|
||||||
|
|
||||||
|
binds = Some(ident);
|
||||||
|
}
|
||||||
|
|
||||||
|
"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() || value == Some(0) {
|
||||||
|
return Err(parse::Error::new(
|
||||||
|
lit.span(),
|
||||||
|
"this literal must be in the range 1...255",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
priority = Some(value.unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
"default" => {
|
||||||
|
if default.is_some() {
|
||||||
|
return Err(parse::Error::new(
|
||||||
|
ident.span(),
|
||||||
|
"argument appears more than once",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let lit: LitBool = content.parse()?;
|
||||||
|
default = Some(lit.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
return Err(parse::Error::new(ident.span(), "unexpected argument"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if content.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle comma: ,
|
||||||
|
let _: Token![,] = content.parse()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let binds = if let Some(r) = binds {
|
||||||
|
r
|
||||||
|
} else {
|
||||||
|
return Err(parse::Error::new(
|
||||||
|
content.span(),
|
||||||
|
"`binds = ...` is missing",
|
||||||
|
));
|
||||||
|
};
|
||||||
|
let default = default.unwrap_or(false);
|
||||||
|
|
||||||
|
Ok(MonotonicArgs {
|
||||||
|
binds,
|
||||||
|
priority,
|
||||||
|
default,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.parse2(tokens)
|
||||||
|
}
|
539
macros/src/syntax/parse/app.rs
Normal file
539
macros/src/syntax/parse/app.rs
Normal file
|
@ -0,0 +1,539 @@
|
||||||
|
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, Type, Visibility,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::Input;
|
||||||
|
use crate::syntax::{
|
||||||
|
ast::{
|
||||||
|
App, AppArgs, Dispatcher, Dispatchers, HardwareTask, Idle, IdleArgs, Init, InitArgs,
|
||||||
|
LocalResource, Monotonic, MonotonicArgs, SharedResource, SoftwareTask,
|
||||||
|
},
|
||||||
|
parse::{self as syntax_parse, util},
|
||||||
|
Either, Map, Set, Settings,
|
||||||
|
};
|
||||||
|
|
||||||
|
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, settings: &Settings) -> 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 monotonics = 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 monotonic_types = HashSet::<Type>::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(())
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut check_monotonic = |ty: &Type| {
|
||||||
|
if monotonic_types.contains(ty) {
|
||||||
|
return Err(parse::Error::new(
|
||||||
|
ty.span(),
|
||||||
|
"this type is already used by another monotonic",
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
monotonic_types.insert(ty.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, settings)? {
|
||||||
|
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,
|
||||||
|
settings,
|
||||||
|
)? {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
Item::Type(ref mut type_item) => {
|
||||||
|
// Match types with the attribute #[monotonic]
|
||||||
|
if let Some(pos) = type_item
|
||||||
|
.attrs
|
||||||
|
.iter()
|
||||||
|
.position(|attr| util::attr_eq(attr, "monotonic"))
|
||||||
|
{
|
||||||
|
let span = type_item.ident.span();
|
||||||
|
|
||||||
|
if monotonics.contains_key(&type_item.ident) {
|
||||||
|
return Err(parse::Error::new(
|
||||||
|
span,
|
||||||
|
"`#[monotonic(...)]` on a specific type must appear at most once",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if type_item.vis != Visibility::Inherited {
|
||||||
|
return Err(parse::Error::new(
|
||||||
|
type_item.span(),
|
||||||
|
"this item must have inherited / private visibility",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
check_monotonic(&*type_item.ty)?;
|
||||||
|
|
||||||
|
let m = type_item.attrs.remove(pos);
|
||||||
|
let args = MonotonicArgs::parse(m)?;
|
||||||
|
|
||||||
|
check_binding(&args.binds)?;
|
||||||
|
|
||||||
|
let monotonic = Monotonic::parse(args, type_item, span)?;
|
||||||
|
|
||||||
|
monotonics.insert(type_item.ident.clone(), monotonic);
|
||||||
|
}
|
||||||
|
|
||||||
|
// All types are passed on
|
||||||
|
user_code.push(item.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,
|
||||||
|
monotonics,
|
||||||
|
shared_resources,
|
||||||
|
local_resources,
|
||||||
|
user_imports,
|
||||||
|
user_code,
|
||||||
|
hardware_tasks,
|
||||||
|
software_tasks,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
96
macros/src/syntax/parse/hardware_task.rs
Normal file
96
macros/src/syntax/parse/hardware_task.rs
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
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 name == "init" || name == "idle" {
|
||||||
|
return Err(parse::Error::new(
|
||||||
|
span,
|
||||||
|
"tasks cannot be named `init` or `idle`",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
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({}::Context)`",
|
||||||
|
name
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 name == "init" || name == "idle" {
|
||||||
|
return Err(parse::Error::new(
|
||||||
|
span,
|
||||||
|
"tasks cannot be named `init` or `idle`",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
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({}::Context)`",
|
||||||
|
name
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
45
macros/src/syntax/parse/idle.rs
Normal file
45
macros/src/syntax/parse/idle.rs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
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({}::Context) -> !`",
|
||||||
|
name
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
52
macros/src/syntax/parse/init.rs
Normal file
52
macros/src/syntax/parse/init.rs
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
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, &name)
|
||||||
|
{
|
||||||
|
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({}::Context) -> (Shared resources struct, Local resources struct, {0}::Monotonics)`",
|
||||||
|
name
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
42
macros/src/syntax/parse/monotonic.rs
Normal file
42
macros/src/syntax/parse/monotonic.rs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
use proc_macro2::Span;
|
||||||
|
use syn::Attribute;
|
||||||
|
use syn::{parse, spanned::Spanned, ItemType, Visibility};
|
||||||
|
|
||||||
|
use crate::syntax::parse::util::FilterAttrs;
|
||||||
|
use crate::syntax::{
|
||||||
|
ast::{Monotonic, MonotonicArgs},
|
||||||
|
parse::util,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl MonotonicArgs {
|
||||||
|
pub(crate) fn parse(attr: Attribute) -> parse::Result<Self> {
|
||||||
|
crate::syntax::parse::monotonic_args(attr.path, attr.tokens)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Monotonic {
|
||||||
|
pub(crate) fn parse(args: MonotonicArgs, item: &ItemType, 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, .. } = util::filter_attributes(item.attrs.clone());
|
||||||
|
|
||||||
|
if !attrs.is_empty() {
|
||||||
|
return Err(parse::Error::new(
|
||||||
|
attrs[0].path.span(),
|
||||||
|
"Monotonic does not support attributes other than `#[cfg]`",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Monotonic {
|
||||||
|
cfgs,
|
||||||
|
ident: item.ident.clone(),
|
||||||
|
ty: item.ty.clone(),
|
||||||
|
args,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
55
macros/src/syntax/parse/resource.rs
Normal file
55
macros/src/syntax/parse/resource.rs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
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()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
86
macros/src/syntax/parse/software_task.rs
Normal file
86
macros/src/syntax/parse/software_task.rs
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
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);
|
||||||
|
|
||||||
|
let span = item.sig.ident.span();
|
||||||
|
|
||||||
|
let name = item.sig.ident.to_string();
|
||||||
|
|
||||||
|
let is_async = item.sig.asyncness.is_some();
|
||||||
|
|
||||||
|
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,
|
||||||
|
is_async,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(parse::Error::new(
|
||||||
|
span,
|
||||||
|
&format!(
|
||||||
|
"this task handler must have type signature `(async) fn({}::Context, ..)`",
|
||||||
|
name
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
let span = item.sig.ident.span();
|
||||||
|
|
||||||
|
let name = item.sig.ident.to_string();
|
||||||
|
|
||||||
|
let is_async = item.sig.asyncness.is_some();
|
||||||
|
|
||||||
|
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,
|
||||||
|
is_async,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(parse::Error::new(
|
||||||
|
span,
|
||||||
|
&format!(
|
||||||
|
"this task handler must have type signature `(async) fn({}::Context, ..)`",
|
||||||
|
name
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
338
macros/src/syntax/parse/util.rs
Normal file
338
macros/src/syntax/parse/util.rs
Normal file
|
@ -0,0 +1,338 @@
|
||||||
|
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, name: &str) -> 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, {name}::Monotonics)
|
||||||
|
//
|
||||||
|
// We check the length and the last one here, analysis checks that the user
|
||||||
|
// provided structs are correct.
|
||||||
|
if t.elems.len() == 3 && type_is_path(&t.elems[2], &[name, "Monotonics"]) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
7
macros/tests/ui.rs
Normal file
7
macros/tests/ui.rs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
use trybuild::TestCases;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ui() {
|
||||||
|
let t = TestCases::new();
|
||||||
|
t.compile_fail("ui/*.rs");
|
||||||
|
}
|
28
macros/ui/async-local-resouces.rs
Normal file
28
macros/ui/async-local-resouces.rs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
#[rtic_macros::mock_app(device = mock)]
|
||||||
|
mod app {
|
||||||
|
#[shared]
|
||||||
|
struct Shared {
|
||||||
|
#[lock_free]
|
||||||
|
e: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[local]
|
||||||
|
struct Local {}
|
||||||
|
|
||||||
|
#[init]
|
||||||
|
fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {}
|
||||||
|
|
||||||
|
// e ok
|
||||||
|
#[task(priority = 1, shared = [e])]
|
||||||
|
fn uart0(cx: uart0::Context) {}
|
||||||
|
|
||||||
|
// e ok
|
||||||
|
#[task(priority = 1, shared = [e])]
|
||||||
|
fn uart1(cx: uart1::Context) {}
|
||||||
|
|
||||||
|
// e not ok
|
||||||
|
#[task(priority = 1, shared = [e])]
|
||||||
|
async fn async_task(cx: async_task::Context) {}
|
||||||
|
}
|
5
macros/ui/async-local-resouces.stderr
Normal file
5
macros/ui/async-local-resouces.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: Lock free shared resource "e" is used by an async tasks, which is forbidden
|
||||||
|
--> ui/async-local-resouces.rs:26:36
|
||||||
|
|
|
||||||
|
26 | #[task(priority = 1, shared = [e])]
|
||||||
|
| ^
|
19
macros/ui/async-zero-prio-tasks.rs
Normal file
19
macros/ui/async-zero-prio-tasks.rs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
#[rtic_macros::mock_app(device = mock)]
|
||||||
|
mod app {
|
||||||
|
#[shared]
|
||||||
|
struct Shared {}
|
||||||
|
|
||||||
|
#[local]
|
||||||
|
struct Local {}
|
||||||
|
|
||||||
|
#[init]
|
||||||
|
fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {}
|
||||||
|
|
||||||
|
#[task(priority = 0)]
|
||||||
|
fn foo(_: foo::Context) {}
|
||||||
|
|
||||||
|
#[idle]
|
||||||
|
fn idle(_: idle::Context) -> ! {}
|
||||||
|
}
|
11
macros/ui/async-zero-prio-tasks.stderr
Normal file
11
macros/ui/async-zero-prio-tasks.stderr
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
error: Software task "foo" has priority 0, but `#[idle]` is defined. 0-priority software tasks are only allowed if there is no `#[idle]`.
|
||||||
|
--> ui/async-zero-prio-tasks.rs:15:8
|
||||||
|
|
|
||||||
|
15 | fn foo(_: foo::Context) {}
|
||||||
|
| ^^^
|
||||||
|
|
||||||
|
error: Software task "foo" has priority 0, but is not `async`. 0-priority software tasks must be `async`.
|
||||||
|
--> ui/async-zero-prio-tasks.rs:15:8
|
||||||
|
|
|
||||||
|
15 | fn foo(_: foo::Context) {}
|
||||||
|
| ^^^
|
16
macros/ui/extern-interrupt-used.rs
Normal file
16
macros/ui/extern-interrupt-used.rs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
#[rtic_macros::mock_app(parse_extern_interrupt, parse_binds, device = mock, dispatchers = [EXTI0])]
|
||||||
|
mod app {
|
||||||
|
#[shared]
|
||||||
|
struct Shared {}
|
||||||
|
|
||||||
|
#[local]
|
||||||
|
struct Local {}
|
||||||
|
|
||||||
|
#[init]
|
||||||
|
fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {}
|
||||||
|
|
||||||
|
#[task(binds = EXTI0)]
|
||||||
|
fn foo(_: foo::Context) {}
|
||||||
|
}
|
5
macros/ui/extern-interrupt-used.stderr
Normal file
5
macros/ui/extern-interrupt-used.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: dispatcher interrupts can't be used as hardware tasks
|
||||||
|
--> $DIR/extern-interrupt-used.rs:14:20
|
||||||
|
|
|
||||||
|
14 | #[task(binds = EXTI0)]
|
||||||
|
| ^^^^^
|
9
macros/ui/idle-double-local.rs
Normal file
9
macros/ui/idle-double-local.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
#[rtic_macros::mock_app(device = mock)]
|
||||||
|
mod app {
|
||||||
|
#[idle(local = [A], local = [B])]
|
||||||
|
fn idle(_: idle::Context) -> ! {
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
}
|
5
macros/ui/idle-double-local.stderr
Normal file
5
macros/ui/idle-double-local.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: argument appears more than once
|
||||||
|
--> $DIR/idle-double-local.rs:5:25
|
||||||
|
|
|
||||||
|
5 | #[idle(local = [A], local = [B])]
|
||||||
|
| ^^^^^
|
9
macros/ui/idle-double-shared.rs
Normal file
9
macros/ui/idle-double-shared.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
#[rtic_macros::mock_app(device = mock)]
|
||||||
|
mod app {
|
||||||
|
#[idle(shared = [A], shared = [B])]
|
||||||
|
fn idle(_: idle::Context) -> ! {
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
}
|
5
macros/ui/idle-double-shared.stderr
Normal file
5
macros/ui/idle-double-shared.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: argument appears more than once
|
||||||
|
--> $DIR/idle-double-shared.rs:5:26
|
||||||
|
|
|
||||||
|
5 | #[idle(shared = [A], shared = [B])]
|
||||||
|
| ^^^^^^
|
9
macros/ui/idle-input.rs
Normal file
9
macros/ui/idle-input.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
#[rtic_macros::mock_app(device = mock)]
|
||||||
|
mod app {
|
||||||
|
#[idle]
|
||||||
|
fn idle(_: idle::Context, _undef: u32) -> ! {
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
}
|
5
macros/ui/idle-input.stderr
Normal file
5
macros/ui/idle-input.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: this `#[idle]` function must have signature `fn(idle::Context) -> !`
|
||||||
|
--> ui/idle-input.rs:6:8
|
||||||
|
|
|
||||||
|
6 | fn idle(_: idle::Context, _undef: u32) -> ! {
|
||||||
|
| ^^^^
|
9
macros/ui/idle-no-context.rs
Normal file
9
macros/ui/idle-no-context.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
#[rtic_macros::mock_app(device = mock)]
|
||||||
|
mod app {
|
||||||
|
#[idle]
|
||||||
|
fn idle() -> ! {
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
}
|
5
macros/ui/idle-no-context.stderr
Normal file
5
macros/ui/idle-no-context.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: this `#[idle]` function must have signature `fn(idle::Context) -> !`
|
||||||
|
--> ui/idle-no-context.rs:6:8
|
||||||
|
|
|
||||||
|
6 | fn idle() -> ! {
|
||||||
|
| ^^^^
|
7
macros/ui/idle-not-divergent.rs
Normal file
7
macros/ui/idle-not-divergent.rs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
#[rtic_macros::mock_app(device = mock)]
|
||||||
|
mod app {
|
||||||
|
#[idle]
|
||||||
|
fn idle(_: idle::Context) {}
|
||||||
|
}
|
5
macros/ui/idle-not-divergent.stderr
Normal file
5
macros/ui/idle-not-divergent.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: this `#[idle]` function must have signature `fn(idle::Context) -> !`
|
||||||
|
--> ui/idle-not-divergent.rs:6:8
|
||||||
|
|
|
||||||
|
6 | fn idle(_: idle::Context) {}
|
||||||
|
| ^^^^
|
9
macros/ui/idle-output.rs
Normal file
9
macros/ui/idle-output.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
#[rtic_macros::mock_app(device = mock)]
|
||||||
|
mod app {
|
||||||
|
#[idle]
|
||||||
|
fn idle(_: idle::Context) -> u32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
5
macros/ui/idle-output.stderr
Normal file
5
macros/ui/idle-output.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: this `#[idle]` function must have signature `fn(idle::Context) -> !`
|
||||||
|
--> ui/idle-output.rs:6:8
|
||||||
|
|
|
||||||
|
6 | fn idle(_: idle::Context) -> u32 {
|
||||||
|
| ^^^^
|
9
macros/ui/idle-pub.rs
Normal file
9
macros/ui/idle-pub.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
#[rtic_macros::mock_app(device = mock)]
|
||||||
|
mod app {
|
||||||
|
#[idle]
|
||||||
|
pub fn idle(_: idle::Context) -> ! {
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
}
|
5
macros/ui/idle-pub.stderr
Normal file
5
macros/ui/idle-pub.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: this `#[idle]` function must have signature `fn(idle::Context) -> !`
|
||||||
|
--> ui/idle-pub.rs:6:12
|
||||||
|
|
|
||||||
|
6 | pub fn idle(_: idle::Context) -> ! {
|
||||||
|
| ^^^^
|
9
macros/ui/idle-unsafe.rs
Normal file
9
macros/ui/idle-unsafe.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
#[rtic_macros::mock_app(device = mock)]
|
||||||
|
mod app {
|
||||||
|
#[idle]
|
||||||
|
unsafe fn idle(_: idle::Context) -> ! {
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
}
|
5
macros/ui/idle-unsafe.stderr
Normal file
5
macros/ui/idle-unsafe.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: this `#[idle]` function must have signature `fn(idle::Context) -> !`
|
||||||
|
--> ui/idle-unsafe.rs:6:15
|
||||||
|
|
|
||||||
|
6 | unsafe fn idle(_: idle::Context) -> ! {
|
||||||
|
| ^^^^
|
13
macros/ui/init-divergent.rs
Normal file
13
macros/ui/init-divergent.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
#[rtic_macros::mock_app(device = mock)]
|
||||||
|
mod app {
|
||||||
|
#[shared]
|
||||||
|
struct Shared {}
|
||||||
|
|
||||||
|
#[local]
|
||||||
|
struct Local {}
|
||||||
|
|
||||||
|
#[init]
|
||||||
|
fn init(_: init::Context) -> ! {}
|
||||||
|
}
|
5
macros/ui/init-divergent.stderr
Normal file
5
macros/ui/init-divergent.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct, init::Monotonics)`
|
||||||
|
--> $DIR/init-divergent.rs:12:8
|
||||||
|
|
|
||||||
|
12 | fn init(_: init::Context) -> ! {}
|
||||||
|
| ^^^^
|
7
macros/ui/init-double-local.rs
Normal file
7
macros/ui/init-double-local.rs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
#[rtic_macros::mock_app(device = mock)]
|
||||||
|
mod app {
|
||||||
|
#[init(local = [A], local = [B])]
|
||||||
|
fn init(_: init::Context) {}
|
||||||
|
}
|
5
macros/ui/init-double-local.stderr
Normal file
5
macros/ui/init-double-local.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: argument appears more than once
|
||||||
|
--> $DIR/init-double-local.rs:5:25
|
||||||
|
|
|
||||||
|
5 | #[init(local = [A], local = [B])]
|
||||||
|
| ^^^^^
|
7
macros/ui/init-double-shared.rs
Normal file
7
macros/ui/init-double-shared.rs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
#[rtic_macros::mock_app(device = mock)]
|
||||||
|
mod app {
|
||||||
|
#[init(shared = [A], shared = [B])]
|
||||||
|
fn init(_: init::Context) {}
|
||||||
|
}
|
5
macros/ui/init-double-shared.stderr
Normal file
5
macros/ui/init-double-shared.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: unexpected argument
|
||||||
|
--> $DIR/init-double-shared.rs:5:12
|
||||||
|
|
|
||||||
|
5 | #[init(shared = [A], shared = [B])]
|
||||||
|
| ^^^^^^
|
13
macros/ui/init-input.rs
Normal file
13
macros/ui/init-input.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
#[rtic_macros::mock_app(device = mock)]
|
||||||
|
mod app {
|
||||||
|
#[shared]
|
||||||
|
struct Shared {}
|
||||||
|
|
||||||
|
#[local]
|
||||||
|
struct Local {}
|
||||||
|
|
||||||
|
#[init]
|
||||||
|
fn init(_: init::Context, _undef: u32) -> (Shared, Local, init::Monotonics) {}
|
||||||
|
}
|
5
macros/ui/init-input.stderr
Normal file
5
macros/ui/init-input.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct, init::Monotonics)`
|
||||||
|
--> $DIR/init-input.rs:12:8
|
||||||
|
|
|
||||||
|
12 | fn init(_: init::Context, _undef: u32) -> (Shared, Local, init::Monotonics) {}
|
||||||
|
| ^^^^
|
13
macros/ui/init-no-context.rs
Normal file
13
macros/ui/init-no-context.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
#[rtic_macros::mock_app(device = mock)]
|
||||||
|
mod app {
|
||||||
|
#[shared]
|
||||||
|
struct Shared {}
|
||||||
|
|
||||||
|
#[local]
|
||||||
|
struct Local {}
|
||||||
|
|
||||||
|
#[init]
|
||||||
|
fn init() -> (Shared, Local, init::Monotonics) {}
|
||||||
|
}
|
5
macros/ui/init-no-context.stderr
Normal file
5
macros/ui/init-no-context.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct, init::Monotonics)`
|
||||||
|
--> $DIR/init-no-context.rs:12:8
|
||||||
|
|
|
||||||
|
12 | fn init() -> (Shared, Local, init::Monotonics) {}
|
||||||
|
| ^^^^
|
9
macros/ui/init-output.rs
Normal file
9
macros/ui/init-output.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
#[rtic_macros::mock_app(device = mock)]
|
||||||
|
mod app {
|
||||||
|
#[init]
|
||||||
|
fn init(_: init::Context) -> u32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
5
macros/ui/init-output.stderr
Normal file
5
macros/ui/init-output.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct, init::Monotonics)`
|
||||||
|
--> $DIR/init-output.rs:6:8
|
||||||
|
|
|
||||||
|
6 | fn init(_: init::Context) -> u32 {
|
||||||
|
| ^^^^
|
13
macros/ui/init-pub.rs
Normal file
13
macros/ui/init-pub.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
#[rtic_macros::mock_app(device = mock)]
|
||||||
|
mod app {
|
||||||
|
#[shared]
|
||||||
|
struct Shared {}
|
||||||
|
|
||||||
|
#[local]
|
||||||
|
struct Local {}
|
||||||
|
|
||||||
|
#[init]
|
||||||
|
pub fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {}
|
||||||
|
}
|
5
macros/ui/init-pub.stderr
Normal file
5
macros/ui/init-pub.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct, init::Monotonics)`
|
||||||
|
--> $DIR/init-pub.rs:12:12
|
||||||
|
|
|
||||||
|
12 | pub fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {}
|
||||||
|
| ^^^^
|
7
macros/ui/init-unsafe.rs
Normal file
7
macros/ui/init-unsafe.rs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
#[rtic_macros::mock_app(device = mock)]
|
||||||
|
mod app {
|
||||||
|
#[init]
|
||||||
|
unsafe fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {}
|
||||||
|
}
|
5
macros/ui/init-unsafe.stderr
Normal file
5
macros/ui/init-unsafe.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct, init::Monotonics)`
|
||||||
|
--> $DIR/init-unsafe.rs:6:15
|
||||||
|
|
|
||||||
|
6 | unsafe fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {}
|
||||||
|
| ^^^^
|
10
macros/ui/interrupt-double.rs
Normal file
10
macros/ui/interrupt-double.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
#[rtic_macros::mock_app(parse_binds, device = mock)]
|
||||||
|
mod app {
|
||||||
|
#[task(binds = UART0)]
|
||||||
|
fn foo(_: foo::Context) {}
|
||||||
|
|
||||||
|
#[task(binds = UART0)]
|
||||||
|
fn bar(_: bar::Context) {}
|
||||||
|
}
|
5
macros/ui/interrupt-double.stderr
Normal file
5
macros/ui/interrupt-double.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: this interrupt is already bound
|
||||||
|
--> $DIR/interrupt-double.rs:8:20
|
||||||
|
|
|
||||||
|
8 | #[task(binds = UART0)]
|
||||||
|
| ^^^^^
|
21
macros/ui/local-collision-2.rs
Normal file
21
macros/ui/local-collision-2.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
#[rtic_macros::mock_app(device = mock)]
|
||||||
|
mod app {
|
||||||
|
#[shared]
|
||||||
|
struct Shared {}
|
||||||
|
|
||||||
|
#[local]
|
||||||
|
struct Local {
|
||||||
|
a: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bar(_: bar::Context) {}
|
||||||
|
|
||||||
|
#[task(local = [a: u8 = 3])]
|
||||||
|
fn bar(_: bar::Context) {}
|
||||||
|
|
||||||
|
#[init(local = [a: u16 = 2])]
|
||||||
|
fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {}
|
||||||
|
}
|
||||||
|
|
17
macros/ui/local-collision-2.stderr
Normal file
17
macros/ui/local-collision-2.stderr
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
error: Local resource "a" is used by multiple tasks or collides with multiple definitions
|
||||||
|
--> $DIR/local-collision-2.rs:10:9
|
||||||
|
|
|
||||||
|
10 | a: u32,
|
||||||
|
| ^
|
||||||
|
|
||||||
|
error: Local resource "a" is used by multiple tasks or collides with multiple definitions
|
||||||
|
--> $DIR/local-collision-2.rs:18:21
|
||||||
|
|
|
||||||
|
18 | #[init(local = [a: u16 = 2])]
|
||||||
|
| ^
|
||||||
|
|
||||||
|
error: Local resource "a" is used by multiple tasks or collides with multiple definitions
|
||||||
|
--> $DIR/local-collision-2.rs:15:21
|
||||||
|
|
|
||||||
|
15 | #[task(local = [a: u8 = 3])]
|
||||||
|
| ^
|
21
macros/ui/local-collision.rs
Normal file
21
macros/ui/local-collision.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
#[rtic_macros::mock_app(device = mock)]
|
||||||
|
mod app {
|
||||||
|
#[shared]
|
||||||
|
struct Shared {}
|
||||||
|
|
||||||
|
#[local]
|
||||||
|
struct Local {
|
||||||
|
a: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[task(local = [a])]
|
||||||
|
fn foo(_: foo::Context) {}
|
||||||
|
|
||||||
|
#[task(local = [a: u8 = 3])]
|
||||||
|
fn bar(_: bar::Context) {}
|
||||||
|
|
||||||
|
#[init]
|
||||||
|
fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {}
|
||||||
|
}
|
11
macros/ui/local-collision.stderr
Normal file
11
macros/ui/local-collision.stderr
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
error: Local resource "a" is used by multiple tasks or collides with multiple definitions
|
||||||
|
--> $DIR/local-collision.rs:10:9
|
||||||
|
|
|
||||||
|
10 | a: u32,
|
||||||
|
| ^
|
||||||
|
|
||||||
|
error: Local resource "a" is used by multiple tasks or collides with multiple definitions
|
||||||
|
--> $DIR/local-collision.rs:16:21
|
||||||
|
|
|
||||||
|
16 | #[task(local = [a: u8 = 3])]
|
||||||
|
| ^
|
16
macros/ui/local-malformed-1.rs
Normal file
16
macros/ui/local-malformed-1.rs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
#[rtic_macros::mock_app(device = mock)]
|
||||||
|
mod app {
|
||||||
|
#[shared]
|
||||||
|
struct Shared {}
|
||||||
|
|
||||||
|
#[local]
|
||||||
|
struct Local {}
|
||||||
|
|
||||||
|
#[task(local = [a:])]
|
||||||
|
fn foo(_: foo::Context) {}
|
||||||
|
|
||||||
|
#[init]
|
||||||
|
fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {}
|
||||||
|
}
|
5
macros/ui/local-malformed-1.stderr
Normal file
5
macros/ui/local-malformed-1.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: unexpected end of input, expected one of: `for`, parentheses, `fn`, `unsafe`, `extern`, identifier, `::`, `<`, square brackets, `*`, `&`, `!`, `impl`, `_`, lifetime
|
||||||
|
--> ui/local-malformed-1.rs:11:23
|
||||||
|
|
|
||||||
|
11 | #[task(local = [a:])]
|
||||||
|
| ^
|
16
macros/ui/local-malformed-2.rs
Normal file
16
macros/ui/local-malformed-2.rs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
#[rtic_macros::mock_app(device = mock)]
|
||||||
|
mod app {
|
||||||
|
#[shared]
|
||||||
|
struct Shared {}
|
||||||
|
|
||||||
|
#[local]
|
||||||
|
struct Local {}
|
||||||
|
|
||||||
|
#[task(local = [a: u32])]
|
||||||
|
fn foo(_: foo::Context) {}
|
||||||
|
|
||||||
|
#[init]
|
||||||
|
fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {}
|
||||||
|
}
|
5
macros/ui/local-malformed-2.stderr
Normal file
5
macros/ui/local-malformed-2.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: malformed, expected 'IDENT: TYPE = EXPR'
|
||||||
|
--> ui/local-malformed-2.rs:11:21
|
||||||
|
|
|
||||||
|
11 | #[task(local = [a: u32])]
|
||||||
|
| ^
|
16
macros/ui/local-malformed-3.rs
Normal file
16
macros/ui/local-malformed-3.rs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
#[rtic_macros::mock_app(device = mock)]
|
||||||
|
mod app {
|
||||||
|
#[shared]
|
||||||
|
struct Shared {}
|
||||||
|
|
||||||
|
#[local]
|
||||||
|
struct Local {}
|
||||||
|
|
||||||
|
#[task(local = [a: u32 =])]
|
||||||
|
fn foo(_: foo::Context) {}
|
||||||
|
|
||||||
|
#[init]
|
||||||
|
fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {}
|
||||||
|
}
|
5
macros/ui/local-malformed-3.stderr
Normal file
5
macros/ui/local-malformed-3.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: unexpected end of input, expected expression
|
||||||
|
--> ui/local-malformed-3.rs:11:29
|
||||||
|
|
|
||||||
|
11 | #[task(local = [a: u32 =])]
|
||||||
|
| ^
|
16
macros/ui/local-malformed-4.rs
Normal file
16
macros/ui/local-malformed-4.rs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
#[rtic_macros::mock_app(device = mock)]
|
||||||
|
mod app {
|
||||||
|
#[shared]
|
||||||
|
struct Shared {}
|
||||||
|
|
||||||
|
#[local]
|
||||||
|
struct Local {}
|
||||||
|
|
||||||
|
#[task(local = [a = u32])]
|
||||||
|
fn foo(_: foo::Context) {}
|
||||||
|
|
||||||
|
#[init]
|
||||||
|
fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {}
|
||||||
|
}
|
5
macros/ui/local-malformed-4.stderr
Normal file
5
macros/ui/local-malformed-4.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: malformed, expected a type
|
||||||
|
--> ui/local-malformed-4.rs:11:21
|
||||||
|
|
|
||||||
|
11 | #[task(local = [a = u32])]
|
||||||
|
| ^
|
16
macros/ui/local-not-declared.rs
Normal file
16
macros/ui/local-not-declared.rs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
#[rtic_macros::mock_app(device = mock)]
|
||||||
|
mod app {
|
||||||
|
#[shared]
|
||||||
|
struct Shared {}
|
||||||
|
|
||||||
|
#[local]
|
||||||
|
struct Local {}
|
||||||
|
|
||||||
|
#[task(local = [A])]
|
||||||
|
fn foo(_: foo::Context) {}
|
||||||
|
|
||||||
|
#[init]
|
||||||
|
fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {}
|
||||||
|
}
|
5
macros/ui/local-not-declared.stderr
Normal file
5
macros/ui/local-not-declared.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: this local resource has NOT been declared
|
||||||
|
--> $DIR/local-not-declared.rs:11:21
|
||||||
|
|
|
||||||
|
11 | #[task(local = [A])]
|
||||||
|
| ^
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue