mirror of
https://github.com/rtic-rs/rtic.git
synced 2025-12-18 05:45:19 +01:00
Move rtic macros to repo root, tune xtask
This commit is contained in:
parent
4124fbdd61
commit
9e445b3583
134 changed files with 31 additions and 29 deletions
2
rtic-macros/.gitignore
vendored
Normal file
2
rtic-macros/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
/Cargo.lock
|
||||
41
rtic-macros/Cargo.toml
Normal file
41
rtic-macros/Cargo.toml
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
[package]
|
||||
authors = [
|
||||
"The Real-Time Interrupt-driven Concurrency developers",
|
||||
"Emil Fresk <emil.fresk@gmail.com>",
|
||||
"Henrik Tjäder <henrik@tjaders.com>",
|
||||
"Jorge Aparicio <jorge@japaric.io>",
|
||||
"Per Lindgren <per.lindgren@ltu.se>",
|
||||
]
|
||||
categories = ["concurrency", "embedded", "no-std", "asynchronous"]
|
||||
description = "Procedural macros, syntax parsing, and codegen of the RTIC crate"
|
||||
documentation = "https://rtic-rs.github.io/rtic/api/rtic"
|
||||
edition = "2021"
|
||||
keywords = ["arm", "cortex-m", "risc-v", "embedded", "async", "runtime", "futures", "await", "no-std", "rtos", "bare-metal"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
name = "rtic-macros"
|
||||
readme = "../../README.md"
|
||||
repository = "https://github.com/rtic-rs/rtic"
|
||||
|
||||
version = "2.0.0-alpha.0"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
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"
|
||||
49
rtic-macros/src/analyze.rs
Normal file
49
rtic-macros/src/analyze.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
use core::ops;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use crate::syntax::{
|
||||
analyze::{self, Priority},
|
||||
ast::{App, Dispatcher},
|
||||
};
|
||||
use syn::Ident;
|
||||
|
||||
/// Extend the upstream `Analysis` struct with our field
|
||||
pub struct Analysis {
|
||||
parent: analyze::Analysis,
|
||||
pub interrupts: BTreeMap<Priority, (Ident, Dispatcher)>,
|
||||
}
|
||||
|
||||
impl ops::Deref for Analysis {
|
||||
type Target = analyze::Analysis;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.parent
|
||||
}
|
||||
}
|
||||
|
||||
// Assign an interrupt to each priority level
|
||||
pub fn app(analysis: analyze::Analysis, app: &App) -> Analysis {
|
||||
let mut available_interrupt = app.args.dispatchers.clone();
|
||||
|
||||
// the set of priorities (each priority only once)
|
||||
let priorities = app
|
||||
.software_tasks
|
||||
.values()
|
||||
.map(|task| task.args.priority)
|
||||
.collect::<BTreeSet<_>>();
|
||||
|
||||
// map from priorities to interrupts (holding name and attributes)
|
||||
|
||||
let interrupts: BTreeMap<Priority, _> = priorities
|
||||
.iter()
|
||||
.filter(|prio| **prio > 0) // 0 prio tasks are run in main
|
||||
.copied()
|
||||
.rev()
|
||||
.map(|p| (p, available_interrupt.pop().expect("UNREACHABLE")))
|
||||
.collect();
|
||||
|
||||
Analysis {
|
||||
parent: analysis,
|
||||
interrupts,
|
||||
}
|
||||
}
|
||||
1
rtic-macros/src/bindings.rs
Normal file
1
rtic-macros/src/bindings.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
|
||||
70
rtic-macros/src/check.rs
Normal file
70
rtic-macros/src/check.rs
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
use std::collections::HashSet;
|
||||
|
||||
use crate::syntax::ast::App;
|
||||
use syn::parse;
|
||||
|
||||
pub fn app(app: &App) -> parse::Result<()> {
|
||||
// Check that external (device-specific) interrupts are not named after known (Cortex-M)
|
||||
// exceptions
|
||||
for name in app.args.dispatchers.keys() {
|
||||
let name_s = name.to_string();
|
||||
|
||||
match &*name_s {
|
||||
"NonMaskableInt" | "HardFault" | "MemoryManagement" | "BusFault" | "UsageFault"
|
||||
| "SecureFault" | "SVCall" | "DebugMonitor" | "PendSV" | "SysTick" => {
|
||||
return Err(parse::Error::new(
|
||||
name.span(),
|
||||
"Cortex-M exceptions can't be used as `extern` interrupts",
|
||||
));
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Check that there are enough external interrupts to dispatch the software tasks and the timer
|
||||
// queue handler
|
||||
let mut first = None;
|
||||
let priorities = app
|
||||
.software_tasks
|
||||
.iter()
|
||||
.map(|(name, task)| {
|
||||
first = Some(name);
|
||||
task.args.priority
|
||||
})
|
||||
.filter(|prio| *prio > 0)
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let need = priorities.len();
|
||||
let given = app.args.dispatchers.len();
|
||||
if need > given {
|
||||
let s = {
|
||||
format!(
|
||||
"not enough interrupts to dispatch \
|
||||
all software tasks (need: {need}; given: {given})"
|
||||
)
|
||||
};
|
||||
|
||||
// If not enough tasks and first still is None, may cause
|
||||
// "custom attribute panicked" due to unwrap on None
|
||||
return Err(parse::Error::new(first.unwrap().span(), s));
|
||||
}
|
||||
|
||||
// Check that all exceptions are valid; only exceptions with configurable priorities are
|
||||
// accepted
|
||||
for (name, task) in &app.hardware_tasks {
|
||||
let name_s = task.args.binds.to_string();
|
||||
match &*name_s {
|
||||
"NonMaskableInt" | "HardFault" => {
|
||||
return Err(parse::Error::new(
|
||||
name.span(),
|
||||
"only exceptions with configurable priority can be used as hardware tasks",
|
||||
));
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
75
rtic-macros/src/codegen.rs
Normal file
75
rtic-macros/src/codegen.rs
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
|
||||
use crate::analyze::Analysis;
|
||||
use crate::syntax::ast::App;
|
||||
|
||||
mod assertions;
|
||||
mod async_dispatchers;
|
||||
mod hardware_tasks;
|
||||
mod idle;
|
||||
mod init;
|
||||
mod local_resources;
|
||||
mod local_resources_struct;
|
||||
mod module;
|
||||
mod post_init;
|
||||
mod pre_init;
|
||||
mod shared_resources;
|
||||
mod shared_resources_struct;
|
||||
mod software_tasks;
|
||||
mod util;
|
||||
|
||||
mod main;
|
||||
|
||||
// TODO: organize codegen to actual parts of code
|
||||
// so `main::codegen` generates ALL the code for `fn main`,
|
||||
// `software_tasks::codegen` generates ALL the code for software tasks etc...
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub fn app(app: &App, analysis: &Analysis) -> TokenStream2 {
|
||||
// Generate the `main` function
|
||||
let main = main::codegen(app, analysis);
|
||||
let init_codegen = init::codegen(app, analysis);
|
||||
let idle_codegen = idle::codegen(app, analysis);
|
||||
let shared_resources_codegen = shared_resources::codegen(app, analysis);
|
||||
let local_resources_codegen = local_resources::codegen(app, analysis);
|
||||
let hardware_tasks_codegen = hardware_tasks::codegen(app, analysis);
|
||||
let software_tasks_codegen = software_tasks::codegen(app, analysis);
|
||||
let async_dispatchers_codegen = async_dispatchers::codegen(app, analysis);
|
||||
|
||||
let user_imports = &app.user_imports;
|
||||
let user_code = &app.user_code;
|
||||
let name = &app.name;
|
||||
let device = &app.args.device;
|
||||
|
||||
let rt_err = util::rt_err_ident();
|
||||
|
||||
quote!(
|
||||
/// The RTIC application module
|
||||
pub mod #name {
|
||||
/// Always include the device crate which contains the vector table
|
||||
use #device as #rt_err;
|
||||
|
||||
#(#user_imports)*
|
||||
|
||||
#(#user_code)*
|
||||
/// User code end
|
||||
|
||||
#init_codegen
|
||||
|
||||
#idle_codegen
|
||||
|
||||
#hardware_tasks_codegen
|
||||
|
||||
#software_tasks_codegen
|
||||
|
||||
#shared_resources_codegen
|
||||
|
||||
#local_resources_codegen
|
||||
|
||||
#async_dispatchers_codegen
|
||||
|
||||
#main
|
||||
}
|
||||
)
|
||||
}
|
||||
53
rtic-macros/src/codegen/assertions.rs
Normal file
53
rtic-macros/src/codegen/assertions.rs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
|
||||
use crate::syntax::ast::App;
|
||||
use crate::{analyze::Analysis, codegen::util};
|
||||
|
||||
/// Generates compile-time assertions that check that types implement the `Send` / `Sync` traits
|
||||
pub fn codegen(app: &App, analysis: &Analysis) -> Vec<TokenStream2> {
|
||||
let mut stmts = vec![];
|
||||
|
||||
for ty in &analysis.send_types {
|
||||
stmts.push(quote!(rtic::export::assert_send::<#ty>();));
|
||||
}
|
||||
|
||||
for ty in &analysis.sync_types {
|
||||
stmts.push(quote!(rtic::export::assert_sync::<#ty>();));
|
||||
}
|
||||
|
||||
let device = &app.args.device;
|
||||
let chunks_name = util::priority_mask_chunks_ident();
|
||||
let no_basepri_checks: Vec<_> = app
|
||||
.hardware_tasks
|
||||
.iter()
|
||||
.filter_map(|(_, task)| {
|
||||
if !util::is_exception(&task.args.binds) {
|
||||
let interrupt_name = &task.args.binds;
|
||||
Some(quote!(
|
||||
if (#device::Interrupt::#interrupt_name as usize) >= (#chunks_name * 32) {
|
||||
::core::panic!("An interrupt out of range is used while in armv6 or armv8m.base");
|
||||
}
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let const_check = quote! {
|
||||
const _CONST_CHECK: () = {
|
||||
if !rtic::export::have_basepri() {
|
||||
#(#no_basepri_checks)*
|
||||
} else {
|
||||
// TODO: Add armv7 checks here
|
||||
}
|
||||
};
|
||||
|
||||
let _ = _CONST_CHECK;
|
||||
};
|
||||
|
||||
stmts.push(const_check);
|
||||
|
||||
stmts
|
||||
}
|
||||
89
rtic-macros/src/codegen/async_dispatchers.rs
Normal file
89
rtic-macros/src/codegen/async_dispatchers.rs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
use crate::syntax::ast::App;
|
||||
use crate::{analyze::Analysis, codegen::util};
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
|
||||
/// Generates task dispatchers
|
||||
pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 {
|
||||
let mut items = vec![];
|
||||
|
||||
let interrupts = &analysis.interrupts;
|
||||
|
||||
// Generate executor definition and priority in global scope
|
||||
for (name, _) in app.software_tasks.iter() {
|
||||
let type_name = util::internal_task_ident(name, "F");
|
||||
let exec_name = util::internal_task_ident(name, "EXEC");
|
||||
|
||||
items.push(quote!(
|
||||
#[allow(non_camel_case_types)]
|
||||
type #type_name = impl core::future::Future;
|
||||
#[allow(non_upper_case_globals)]
|
||||
static #exec_name: rtic::export::executor::AsyncTaskExecutor<#type_name> =
|
||||
rtic::export::executor::AsyncTaskExecutor::new();
|
||||
));
|
||||
}
|
||||
|
||||
for (&level, channel) in &analysis.channels {
|
||||
let mut stmts = vec![];
|
||||
|
||||
let dispatcher_name = if level > 0 {
|
||||
util::suffixed(&interrupts.get(&level).expect("UNREACHABLE").0.to_string())
|
||||
} else {
|
||||
util::zero_prio_dispatcher_ident()
|
||||
};
|
||||
|
||||
let pend_interrupt = if level > 0 {
|
||||
let device = &app.args.device;
|
||||
let enum_ = util::interrupt_ident();
|
||||
|
||||
quote!(rtic::pend(#device::#enum_::#dispatcher_name);)
|
||||
} else {
|
||||
// For 0 priority tasks we don't need to pend anything
|
||||
quote!()
|
||||
};
|
||||
|
||||
for name in channel.tasks.iter() {
|
||||
let exec_name = util::internal_task_ident(name, "EXEC");
|
||||
// TODO: Fix cfg
|
||||
// let task = &app.software_tasks[name];
|
||||
// let cfgs = &task.cfgs;
|
||||
|
||||
stmts.push(quote!(
|
||||
#exec_name.poll(|| {
|
||||
#exec_name.set_pending();
|
||||
#pend_interrupt
|
||||
});
|
||||
));
|
||||
}
|
||||
|
||||
if level > 0 {
|
||||
let doc = format!("Interrupt handler to dispatch async tasks at priority {level}");
|
||||
let attribute = &interrupts.get(&level).expect("UNREACHABLE").1.attrs;
|
||||
items.push(quote!(
|
||||
#[allow(non_snake_case)]
|
||||
#[doc = #doc]
|
||||
#[no_mangle]
|
||||
#(#attribute)*
|
||||
unsafe fn #dispatcher_name() {
|
||||
/// The priority of this interrupt handler
|
||||
const PRIORITY: u8 = #level;
|
||||
|
||||
rtic::export::run(PRIORITY, || {
|
||||
#(#stmts)*
|
||||
});
|
||||
}
|
||||
));
|
||||
} else {
|
||||
items.push(quote!(
|
||||
#[allow(non_snake_case)]
|
||||
unsafe fn #dispatcher_name() -> ! {
|
||||
loop {
|
||||
#(#stmts)*
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
quote!(#(#items)*)
|
||||
}
|
||||
87
rtic-macros/src/codegen/hardware_tasks.rs
Normal file
87
rtic-macros/src/codegen/hardware_tasks.rs
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
use crate::syntax::{ast::App, Context};
|
||||
use crate::{
|
||||
analyze::Analysis,
|
||||
codegen::{local_resources_struct, module, shared_resources_struct},
|
||||
};
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
|
||||
/// Generate support code for hardware tasks (`#[exception]`s and `#[interrupt]`s)
|
||||
pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 {
|
||||
let mut mod_app = vec![];
|
||||
let mut root = vec![];
|
||||
let mut user_tasks = vec![];
|
||||
|
||||
for (name, task) in &app.hardware_tasks {
|
||||
let symbol = task.args.binds.clone();
|
||||
let priority = task.args.priority;
|
||||
let cfgs = &task.cfgs;
|
||||
let attrs = &task.attrs;
|
||||
|
||||
mod_app.push(quote!(
|
||||
#[allow(non_snake_case)]
|
||||
#[no_mangle]
|
||||
#(#attrs)*
|
||||
#(#cfgs)*
|
||||
unsafe fn #symbol() {
|
||||
const PRIORITY: u8 = #priority;
|
||||
|
||||
rtic::export::run(PRIORITY, || {
|
||||
#name(
|
||||
#name::Context::new()
|
||||
)
|
||||
});
|
||||
}
|
||||
));
|
||||
|
||||
// `${task}Locals`
|
||||
if !task.args.local_resources.is_empty() {
|
||||
let (item, constructor) =
|
||||
local_resources_struct::codegen(Context::HardwareTask(name), app);
|
||||
|
||||
root.push(item);
|
||||
|
||||
mod_app.push(constructor);
|
||||
}
|
||||
|
||||
// `${task}Resources`
|
||||
if !task.args.shared_resources.is_empty() {
|
||||
let (item, constructor) =
|
||||
shared_resources_struct::codegen(Context::HardwareTask(name), app);
|
||||
|
||||
root.push(item);
|
||||
|
||||
mod_app.push(constructor);
|
||||
}
|
||||
|
||||
// Module generation...
|
||||
|
||||
root.push(module::codegen(Context::HardwareTask(name), app, analysis));
|
||||
|
||||
// End module generation
|
||||
|
||||
if !task.is_extern {
|
||||
let attrs = &task.attrs;
|
||||
let context = &task.context;
|
||||
let stmts = &task.stmts;
|
||||
user_tasks.push(quote!(
|
||||
#(#attrs)*
|
||||
#[allow(non_snake_case)]
|
||||
fn #name(#context: #name::Context) {
|
||||
use rtic::Mutex as _;
|
||||
use rtic::mutex::prelude::*;
|
||||
|
||||
#(#stmts)*
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
quote!(
|
||||
#(#mod_app)*
|
||||
|
||||
#(#root)*
|
||||
|
||||
#(#user_tasks)*
|
||||
)
|
||||
}
|
||||
58
rtic-macros/src/codegen/idle.rs
Normal file
58
rtic-macros/src/codegen/idle.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
use crate::syntax::{ast::App, Context};
|
||||
use crate::{
|
||||
analyze::Analysis,
|
||||
codegen::{local_resources_struct, module, shared_resources_struct},
|
||||
};
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
|
||||
/// Generates support code for `#[idle]` functions
|
||||
pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 {
|
||||
if let Some(idle) = &app.idle {
|
||||
let mut mod_app = vec![];
|
||||
let mut root_idle = vec![];
|
||||
|
||||
let name = &idle.name;
|
||||
|
||||
if !idle.args.shared_resources.is_empty() {
|
||||
let (item, constructor) = shared_resources_struct::codegen(Context::Idle, app);
|
||||
|
||||
root_idle.push(item);
|
||||
mod_app.push(constructor);
|
||||
}
|
||||
|
||||
if !idle.args.local_resources.is_empty() {
|
||||
let (item, constructor) = local_resources_struct::codegen(Context::Idle, app);
|
||||
|
||||
root_idle.push(item);
|
||||
|
||||
mod_app.push(constructor);
|
||||
}
|
||||
|
||||
root_idle.push(module::codegen(Context::Idle, app, analysis));
|
||||
|
||||
let attrs = &idle.attrs;
|
||||
let context = &idle.context;
|
||||
let stmts = &idle.stmts;
|
||||
let user_idle = Some(quote!(
|
||||
#(#attrs)*
|
||||
#[allow(non_snake_case)]
|
||||
fn #name(#context: #name::Context) -> ! {
|
||||
use rtic::Mutex as _;
|
||||
use rtic::mutex::prelude::*;
|
||||
|
||||
#(#stmts)*
|
||||
}
|
||||
));
|
||||
|
||||
quote!(
|
||||
#(#mod_app)*
|
||||
|
||||
#(#root_idle)*
|
||||
|
||||
#user_idle
|
||||
)
|
||||
} else {
|
||||
quote!()
|
||||
}
|
||||
}
|
||||
95
rtic-macros/src/codegen/init.rs
Normal file
95
rtic-macros/src/codegen/init.rs
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
|
||||
use crate::{
|
||||
analyze::Analysis,
|
||||
codegen::{local_resources_struct, module},
|
||||
syntax::{ast::App, Context},
|
||||
};
|
||||
|
||||
/// Generates support code for `#[init]` functions
|
||||
pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 {
|
||||
let init = &app.init;
|
||||
let name = &init.name;
|
||||
|
||||
let mut root_init = vec![];
|
||||
|
||||
let context = &init.context;
|
||||
let attrs = &init.attrs;
|
||||
let stmts = &init.stmts;
|
||||
let shared = &init.user_shared_struct;
|
||||
let local = &init.user_local_struct;
|
||||
|
||||
let shared_resources: Vec<_> = app
|
||||
.shared_resources
|
||||
.iter()
|
||||
.map(|(k, v)| {
|
||||
let ty = &v.ty;
|
||||
let cfgs = &v.cfgs;
|
||||
let docs = &v.docs;
|
||||
quote!(
|
||||
#(#cfgs)*
|
||||
#(#docs)*
|
||||
#k: #ty,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let local_resources: Vec<_> = app
|
||||
.local_resources
|
||||
.iter()
|
||||
.map(|(k, v)| {
|
||||
let ty = &v.ty;
|
||||
let cfgs = &v.cfgs;
|
||||
let docs = &v.docs;
|
||||
quote!(
|
||||
#(#cfgs)*
|
||||
#(#docs)*
|
||||
#k: #ty,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
root_init.push(quote! {
|
||||
struct #shared {
|
||||
#(#shared_resources)*
|
||||
}
|
||||
|
||||
struct #local {
|
||||
#(#local_resources)*
|
||||
}
|
||||
});
|
||||
|
||||
// let locals_pat = locals_pat.iter();
|
||||
|
||||
let user_init_return = quote! {#shared, #local};
|
||||
|
||||
let user_init = quote!(
|
||||
#(#attrs)*
|
||||
#[inline(always)]
|
||||
#[allow(non_snake_case)]
|
||||
fn #name(#context: #name::Context) -> (#user_init_return) {
|
||||
#(#stmts)*
|
||||
}
|
||||
);
|
||||
|
||||
let mut mod_app = None;
|
||||
|
||||
// `${task}Locals`
|
||||
if !init.args.local_resources.is_empty() {
|
||||
let (item, constructor) = local_resources_struct::codegen(Context::Init, app);
|
||||
|
||||
root_init.push(item);
|
||||
|
||||
mod_app = Some(constructor);
|
||||
}
|
||||
|
||||
root_init.push(module::codegen(Context::Init, app, analysis));
|
||||
|
||||
quote!(
|
||||
#mod_app
|
||||
|
||||
#(#root_init)*
|
||||
|
||||
#user_init
|
||||
)
|
||||
}
|
||||
65
rtic-macros/src/codegen/local_resources.rs
Normal file
65
rtic-macros/src/codegen/local_resources.rs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
use crate::syntax::ast::App;
|
||||
use crate::{analyze::Analysis, codegen::util};
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
|
||||
/// Generates `local` variables and local resource proxies
|
||||
///
|
||||
/// I.e. the `static` variables and theirs proxies.
|
||||
pub fn codegen(app: &App, _analysis: &Analysis) -> TokenStream2 {
|
||||
let mut mod_app = vec![];
|
||||
|
||||
// All local resources declared in the `#[local]' struct
|
||||
for (name, res) in &app.local_resources {
|
||||
let cfgs = &res.cfgs;
|
||||
let ty = &res.ty;
|
||||
let mangled_name = util::static_local_resource_ident(name);
|
||||
|
||||
let attrs = &res.attrs;
|
||||
|
||||
// late resources in `util::link_section_uninit`
|
||||
// unless user specifies custom link section
|
||||
let section = if attrs.iter().any(|attr| attr.path.is_ident("link_section")) {
|
||||
None
|
||||
} else {
|
||||
Some(util::link_section_uninit())
|
||||
};
|
||||
|
||||
// For future use
|
||||
// let doc = format!(" RTIC internal: {}:{}", file!(), line!());
|
||||
mod_app.push(quote!(
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
// #[doc = #doc]
|
||||
#[doc(hidden)]
|
||||
#(#attrs)*
|
||||
#(#cfgs)*
|
||||
#section
|
||||
static #mangled_name: rtic::RacyCell<core::mem::MaybeUninit<#ty>> = rtic::RacyCell::new(core::mem::MaybeUninit::uninit());
|
||||
));
|
||||
}
|
||||
|
||||
// All declared `local = [NAME: TY = EXPR]` local resources
|
||||
for (task_name, resource_name, task_local) in app.declared_local_resources() {
|
||||
let cfgs = &task_local.cfgs;
|
||||
let ty = &task_local.ty;
|
||||
let expr = &task_local.expr;
|
||||
let attrs = &task_local.attrs;
|
||||
|
||||
let mangled_name = util::declared_static_local_resource_ident(resource_name, task_name);
|
||||
|
||||
// For future use
|
||||
// let doc = format!(" RTIC internal: {}:{}", file!(), line!());
|
||||
mod_app.push(quote!(
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
// #[doc = #doc]
|
||||
#[doc(hidden)]
|
||||
#(#attrs)*
|
||||
#(#cfgs)*
|
||||
static #mangled_name: rtic::RacyCell<#ty> = rtic::RacyCell::new(#expr);
|
||||
));
|
||||
}
|
||||
|
||||
quote!(#(#mod_app)*)
|
||||
}
|
||||
102
rtic-macros/src/codegen/local_resources_struct.rs
Normal file
102
rtic-macros/src/codegen/local_resources_struct.rs
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
use crate::syntax::{
|
||||
ast::{App, TaskLocal},
|
||||
Context,
|
||||
};
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
|
||||
use crate::codegen::util;
|
||||
|
||||
/// Generates local resources structs
|
||||
pub fn codegen(ctxt: Context, app: &App) -> (TokenStream2, TokenStream2) {
|
||||
let resources = match ctxt {
|
||||
Context::Init => &app.init.args.local_resources,
|
||||
Context::Idle => {
|
||||
&app.idle
|
||||
.as_ref()
|
||||
.expect("RTIC-ICE: unable to get idle name")
|
||||
.args
|
||||
.local_resources
|
||||
}
|
||||
Context::HardwareTask(name) => &app.hardware_tasks[name].args.local_resources,
|
||||
Context::SoftwareTask(name) => &app.software_tasks[name].args.local_resources,
|
||||
};
|
||||
|
||||
let task_name = util::get_task_name(ctxt, app);
|
||||
|
||||
let mut fields = vec![];
|
||||
let mut values = vec![];
|
||||
|
||||
for (name, task_local) in resources {
|
||||
let (cfgs, ty, is_declared) = match task_local {
|
||||
TaskLocal::External => {
|
||||
let r = app.local_resources.get(name).expect("UNREACHABLE");
|
||||
(&r.cfgs, &r.ty, false)
|
||||
}
|
||||
TaskLocal::Declared(r) => (&r.cfgs, &r.ty, true),
|
||||
};
|
||||
|
||||
let lt = if ctxt.runs_once() {
|
||||
quote!('static)
|
||||
} else {
|
||||
quote!('a)
|
||||
};
|
||||
|
||||
let mangled_name = if matches!(task_local, TaskLocal::External) {
|
||||
util::static_local_resource_ident(name)
|
||||
} else {
|
||||
util::declared_static_local_resource_ident(name, &task_name)
|
||||
};
|
||||
|
||||
fields.push(quote!(
|
||||
#(#cfgs)*
|
||||
#[allow(missing_docs)]
|
||||
pub #name: &#lt mut #ty
|
||||
));
|
||||
|
||||
let expr = if is_declared {
|
||||
// If the local resources is already initialized, we only need to access its value and
|
||||
// not go through an `MaybeUninit`
|
||||
quote!(&mut *#mangled_name.get_mut())
|
||||
} else {
|
||||
quote!(&mut *(&mut *#mangled_name.get_mut()).as_mut_ptr())
|
||||
};
|
||||
|
||||
values.push(quote!(
|
||||
#(#cfgs)*
|
||||
#name: #expr
|
||||
));
|
||||
}
|
||||
|
||||
fields.push(quote!(
|
||||
#[doc(hidden)]
|
||||
pub __rtic_internal_marker: ::core::marker::PhantomData<&'a ()>
|
||||
));
|
||||
|
||||
values.push(quote!(__rtic_internal_marker: ::core::marker::PhantomData));
|
||||
|
||||
let doc = format!("Local resources `{}` has access to", ctxt.ident(app));
|
||||
let ident = util::local_resources_ident(ctxt, app);
|
||||
let item = quote!(
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[doc = #doc]
|
||||
pub struct #ident<'a> {
|
||||
#(#fields,)*
|
||||
}
|
||||
);
|
||||
|
||||
let constructor = quote!(
|
||||
impl<'a> #ident<'a> {
|
||||
#[inline(always)]
|
||||
#[allow(missing_docs)]
|
||||
pub unsafe fn new() -> Self {
|
||||
#ident {
|
||||
#(#values,)*
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
(item, constructor)
|
||||
}
|
||||
52
rtic-macros/src/codegen/main.rs
Normal file
52
rtic-macros/src/codegen/main.rs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
use crate::{analyze::Analysis, codegen::util, syntax::ast::App};
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
|
||||
use super::{assertions, post_init, pre_init};
|
||||
|
||||
/// Generates code for `fn main`
|
||||
pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 {
|
||||
let assertion_stmts = assertions::codegen(app, analysis);
|
||||
|
||||
let pre_init_stmts = pre_init::codegen(app, analysis);
|
||||
|
||||
let post_init_stmts = post_init::codegen(app, analysis);
|
||||
|
||||
let call_idle = if let Some(idle) = &app.idle {
|
||||
let name = &idle.name;
|
||||
quote!(#name(#name::Context::new()))
|
||||
} else if analysis.channels.get(&0).is_some() {
|
||||
let dispatcher = util::zero_prio_dispatcher_ident();
|
||||
quote!(#dispatcher();)
|
||||
} else {
|
||||
quote!(loop {
|
||||
rtic::export::nop()
|
||||
})
|
||||
};
|
||||
|
||||
let main = util::suffixed("main");
|
||||
let init_name = &app.init.name;
|
||||
quote!(
|
||||
#[doc(hidden)]
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn #main() -> ! {
|
||||
#(#assertion_stmts)*
|
||||
|
||||
#(#pre_init_stmts)*
|
||||
|
||||
#[inline(never)]
|
||||
fn __rtic_init_resources<F>(f: F) where F: FnOnce() {
|
||||
f();
|
||||
}
|
||||
|
||||
// Wrap late_init_stmts in a function to ensure that stack space is reclaimed.
|
||||
__rtic_init_resources(||{
|
||||
let (shared_resources, local_resources) = #init_name(#init_name::Context::new(core.into()));
|
||||
|
||||
#(#post_init_stmts)*
|
||||
});
|
||||
|
||||
#call_idle
|
||||
}
|
||||
)
|
||||
}
|
||||
197
rtic-macros/src/codegen/module.rs
Normal file
197
rtic-macros/src/codegen/module.rs
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
use crate::syntax::{ast::App, Context};
|
||||
use crate::{analyze::Analysis, codegen::util};
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 {
|
||||
let mut items = vec![];
|
||||
let mut module_items = vec![];
|
||||
let mut fields = vec![];
|
||||
let mut values = vec![];
|
||||
// Used to copy task cfgs to the whole module
|
||||
let mut task_cfgs = vec![];
|
||||
|
||||
let name = ctxt.ident(app);
|
||||
|
||||
match ctxt {
|
||||
Context::Init => {
|
||||
fields.push(quote!(
|
||||
/// Core (Cortex-M) peripherals
|
||||
pub core: rtic::export::Peripherals
|
||||
));
|
||||
|
||||
if app.args.peripherals {
|
||||
let device = &app.args.device;
|
||||
|
||||
fields.push(quote!(
|
||||
/// Device peripherals
|
||||
pub device: #device::Peripherals
|
||||
));
|
||||
|
||||
values.push(quote!(device: #device::Peripherals::steal()));
|
||||
}
|
||||
|
||||
fields.push(quote!(
|
||||
/// Critical section token for init
|
||||
pub cs: rtic::export::CriticalSection<'a>
|
||||
));
|
||||
|
||||
values.push(quote!(cs: rtic::export::CriticalSection::new()));
|
||||
|
||||
values.push(quote!(core));
|
||||
}
|
||||
|
||||
Context::Idle | Context::HardwareTask(_) | Context::SoftwareTask(_) => {}
|
||||
}
|
||||
|
||||
if ctxt.has_local_resources(app) {
|
||||
let ident = util::local_resources_ident(ctxt, app);
|
||||
|
||||
module_items.push(quote!(
|
||||
#[doc(inline)]
|
||||
pub use super::#ident as LocalResources;
|
||||
));
|
||||
|
||||
fields.push(quote!(
|
||||
/// Local Resources this task has access to
|
||||
pub local: #name::LocalResources<'a>
|
||||
));
|
||||
|
||||
values.push(quote!(local: #name::LocalResources::new()));
|
||||
}
|
||||
|
||||
if ctxt.has_shared_resources(app) {
|
||||
let ident = util::shared_resources_ident(ctxt, app);
|
||||
|
||||
module_items.push(quote!(
|
||||
#[doc(inline)]
|
||||
pub use super::#ident as SharedResources;
|
||||
));
|
||||
|
||||
fields.push(quote!(
|
||||
/// Shared Resources this task has access to
|
||||
pub shared: #name::SharedResources<'a>
|
||||
));
|
||||
|
||||
values.push(quote!(shared: #name::SharedResources::new()));
|
||||
}
|
||||
|
||||
let doc = match ctxt {
|
||||
Context::Idle => "Idle loop",
|
||||
Context::Init => "Initialization function",
|
||||
Context::HardwareTask(_) => "Hardware task",
|
||||
Context::SoftwareTask(_) => "Software task",
|
||||
};
|
||||
|
||||
let v = Vec::new();
|
||||
let cfgs = match ctxt {
|
||||
Context::HardwareTask(t) => &app.hardware_tasks[t].cfgs,
|
||||
Context::SoftwareTask(t) => &app.software_tasks[t].cfgs,
|
||||
_ => &v,
|
||||
};
|
||||
|
||||
let core = if ctxt.is_init() {
|
||||
Some(quote!(core: rtic::export::Peripherals,))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let internal_context_name = util::internal_task_ident(name, "Context");
|
||||
let exec_name = util::internal_task_ident(name, "EXEC");
|
||||
|
||||
items.push(quote!(
|
||||
#(#cfgs)*
|
||||
/// Execution context
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct #internal_context_name<'a> {
|
||||
#[doc(hidden)]
|
||||
__rtic_internal_p: ::core::marker::PhantomData<&'a ()>,
|
||||
#(#fields,)*
|
||||
}
|
||||
|
||||
#(#cfgs)*
|
||||
impl<'a> #internal_context_name<'a> {
|
||||
#[inline(always)]
|
||||
#[allow(missing_docs)]
|
||||
pub unsafe fn new(#core) -> Self {
|
||||
#internal_context_name {
|
||||
__rtic_internal_p: ::core::marker::PhantomData,
|
||||
#(#values,)*
|
||||
}
|
||||
}
|
||||
}
|
||||
));
|
||||
|
||||
module_items.push(quote!(
|
||||
#(#cfgs)*
|
||||
#[doc(inline)]
|
||||
pub use super::#internal_context_name as Context;
|
||||
));
|
||||
|
||||
if let Context::SoftwareTask(..) = ctxt {
|
||||
let spawnee = &app.software_tasks[name];
|
||||
let priority = spawnee.args.priority;
|
||||
let cfgs = &spawnee.cfgs;
|
||||
// Store a copy of the task cfgs
|
||||
task_cfgs = cfgs.clone();
|
||||
|
||||
let pend_interrupt = if priority > 0 {
|
||||
let device = &app.args.device;
|
||||
let enum_ = util::interrupt_ident();
|
||||
let interrupt = &analysis.interrupts.get(&priority).expect("UREACHABLE").0;
|
||||
quote!(rtic::pend(#device::#enum_::#interrupt);)
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
let internal_spawn_ident = util::internal_task_ident(name, "spawn");
|
||||
let (input_args, input_tupled, input_untupled, input_ty) =
|
||||
util::regroup_inputs(&spawnee.inputs);
|
||||
|
||||
// Spawn caller
|
||||
items.push(quote!(
|
||||
#(#cfgs)*
|
||||
/// Spawns the task directly
|
||||
#[allow(non_snake_case)]
|
||||
#[doc(hidden)]
|
||||
pub fn #internal_spawn_ident(#(#input_args,)*) -> Result<(), #input_ty> {
|
||||
// SAFETY: If `try_allocate` suceeds one must call `spawn`, which we do.
|
||||
unsafe {
|
||||
if #exec_name.try_allocate() {
|
||||
let f = #name(unsafe { #name::Context::new() } #(,#input_untupled)*);
|
||||
#exec_name.spawn(f);
|
||||
#pend_interrupt
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(#input_tupled)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
));
|
||||
|
||||
module_items.push(quote!(
|
||||
#(#cfgs)*
|
||||
#[doc(inline)]
|
||||
pub use super::#internal_spawn_ident as spawn;
|
||||
));
|
||||
}
|
||||
|
||||
if items.is_empty() {
|
||||
quote!()
|
||||
} else {
|
||||
quote!(
|
||||
#(#items)*
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#(#task_cfgs)*
|
||||
#[doc = #doc]
|
||||
pub mod #name {
|
||||
#(#module_items)*
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
47
rtic-macros/src/codegen/post_init.rs
Normal file
47
rtic-macros/src/codegen/post_init.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
use crate::{analyze::Analysis, codegen::util, syntax::ast::App};
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
|
||||
/// Generates code that runs after `#[init]` returns
|
||||
pub fn codegen(app: &App, analysis: &Analysis) -> Vec<TokenStream2> {
|
||||
let mut stmts = vec![];
|
||||
|
||||
// Initialize shared resources
|
||||
for (name, res) in &app.shared_resources {
|
||||
let mangled_name = util::static_shared_resource_ident(name);
|
||||
// If it's live
|
||||
let cfgs = res.cfgs.clone();
|
||||
if analysis.shared_resources.get(name).is_some() {
|
||||
stmts.push(quote!(
|
||||
// We include the cfgs
|
||||
#(#cfgs)*
|
||||
// Resource is a RacyCell<MaybeUninit<T>>
|
||||
// - `get_mut` to obtain a raw pointer to `MaybeUninit<T>`
|
||||
// - `write` the defined value for the late resource T
|
||||
#mangled_name.get_mut().write(core::mem::MaybeUninit::new(shared_resources.#name));
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize local resources
|
||||
for (name, res) in &app.local_resources {
|
||||
let mangled_name = util::static_local_resource_ident(name);
|
||||
// If it's live
|
||||
let cfgs = res.cfgs.clone();
|
||||
if analysis.local_resources.get(name).is_some() {
|
||||
stmts.push(quote!(
|
||||
// We include the cfgs
|
||||
#(#cfgs)*
|
||||
// Resource is a RacyCell<MaybeUninit<T>>
|
||||
// - `get_mut` to obtain a raw pointer to `MaybeUninit<T>`
|
||||
// - `write` the defined value for the late resource T
|
||||
#mangled_name.get_mut().write(core::mem::MaybeUninit::new(local_resources.#name));
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Enable the interrupts -- this completes the `init`-ialization phase
|
||||
stmts.push(quote!(rtic::export::interrupt::enable();));
|
||||
|
||||
stmts
|
||||
}
|
||||
85
rtic-macros/src/codegen/pre_init.rs
Normal file
85
rtic-macros/src/codegen/pre_init.rs
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
use crate::syntax::ast::App;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
|
||||
use crate::{analyze::Analysis, codegen::util};
|
||||
|
||||
/// Generates code that runs before `#[init]`
|
||||
pub fn codegen(app: &App, analysis: &Analysis) -> Vec<TokenStream2> {
|
||||
let mut stmts = vec![];
|
||||
|
||||
let rt_err = util::rt_err_ident();
|
||||
|
||||
// Disable interrupts -- `init` must run with interrupts disabled
|
||||
stmts.push(quote!(rtic::export::interrupt::disable();));
|
||||
|
||||
stmts.push(quote!(
|
||||
// To set the variable in cortex_m so the peripherals cannot be taken multiple times
|
||||
let mut core: rtic::export::Peripherals = rtic::export::Peripherals::steal().into();
|
||||
));
|
||||
|
||||
let device = &app.args.device;
|
||||
let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS);
|
||||
|
||||
// check that all dispatchers exists in the `Interrupt` enumeration regardless of whether
|
||||
// they are used or not
|
||||
let interrupt = util::interrupt_ident();
|
||||
for name in app.args.dispatchers.keys() {
|
||||
stmts.push(quote!(let _ = #rt_err::#interrupt::#name;));
|
||||
}
|
||||
|
||||
let interrupt_ids = analysis.interrupts.iter().map(|(p, (id, _))| (p, id));
|
||||
|
||||
// Unmask interrupts and set their priorities
|
||||
for (&priority, name) in interrupt_ids.chain(app.hardware_tasks.values().filter_map(|task| {
|
||||
if util::is_exception(&task.args.binds) {
|
||||
// We do exceptions in another pass
|
||||
None
|
||||
} else {
|
||||
Some((&task.args.priority, &task.args.binds))
|
||||
}
|
||||
})) {
|
||||
let es = format!(
|
||||
"Maximum priority used by interrupt vector '{name}' is more than supported by hardware"
|
||||
);
|
||||
// Compile time assert that this priority is supported by the device
|
||||
stmts.push(quote!(
|
||||
const _: () = if (1 << #nvic_prio_bits) < #priority as usize { ::core::panic!(#es); };
|
||||
));
|
||||
|
||||
stmts.push(quote!(
|
||||
core.NVIC.set_priority(
|
||||
#rt_err::#interrupt::#name,
|
||||
rtic::export::logical2hw(#priority, #nvic_prio_bits),
|
||||
);
|
||||
));
|
||||
|
||||
// NOTE unmask the interrupt *after* setting its priority: changing the priority of a pended
|
||||
// interrupt is implementation defined
|
||||
stmts.push(quote!(rtic::export::NVIC::unmask(#rt_err::#interrupt::#name);));
|
||||
}
|
||||
|
||||
// Set exception priorities
|
||||
for (name, priority) in app.hardware_tasks.values().filter_map(|task| {
|
||||
if util::is_exception(&task.args.binds) {
|
||||
Some((&task.args.binds, task.args.priority))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
let es = format!(
|
||||
"Maximum priority used by interrupt vector '{name}' is more than supported by hardware"
|
||||
);
|
||||
// Compile time assert that this priority is supported by the device
|
||||
stmts.push(quote!(
|
||||
const _: () = if (1 << #nvic_prio_bits) < #priority as usize { ::core::panic!(#es); };
|
||||
));
|
||||
|
||||
stmts.push(quote!(core.SCB.set_priority(
|
||||
rtic::export::SystemHandler::#name,
|
||||
rtic::export::logical2hw(#priority, #nvic_prio_bits),
|
||||
);));
|
||||
}
|
||||
|
||||
stmts
|
||||
}
|
||||
183
rtic-macros/src/codegen/shared_resources.rs
Normal file
183
rtic-macros/src/codegen/shared_resources.rs
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
use crate::syntax::{analyze::Ownership, ast::App};
|
||||
use crate::{analyze::Analysis, codegen::util};
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Generates `static` variables and shared resource proxies
|
||||
pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 {
|
||||
let mut mod_app = vec![];
|
||||
let mut mod_resources = vec![];
|
||||
|
||||
for (name, res) in &app.shared_resources {
|
||||
let cfgs = &res.cfgs;
|
||||
let ty = &res.ty;
|
||||
let mangled_name = &util::static_shared_resource_ident(name);
|
||||
|
||||
let attrs = &res.attrs;
|
||||
|
||||
// late resources in `util::link_section_uninit`
|
||||
// unless user specifies custom link section
|
||||
let section = if attrs.iter().any(|attr| attr.path.is_ident("link_section")) {
|
||||
None
|
||||
} else {
|
||||
Some(util::link_section_uninit())
|
||||
};
|
||||
|
||||
// For future use
|
||||
// let doc = format!(" RTIC internal: {}:{}", file!(), line!());
|
||||
mod_app.push(quote!(
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
// #[doc = #doc]
|
||||
#[doc(hidden)]
|
||||
#(#attrs)*
|
||||
#(#cfgs)*
|
||||
#section
|
||||
static #mangled_name: rtic::RacyCell<core::mem::MaybeUninit<#ty>> = rtic::RacyCell::new(core::mem::MaybeUninit::uninit());
|
||||
));
|
||||
|
||||
// For future use
|
||||
// let doc = format!(" RTIC internal: {}:{}", file!(), line!());
|
||||
|
||||
let shared_name = util::need_to_lock_ident(name);
|
||||
|
||||
if !res.properties.lock_free {
|
||||
mod_resources.push(quote!(
|
||||
// #[doc = #doc]
|
||||
#[doc(hidden)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#(#cfgs)*
|
||||
pub struct #shared_name<'a> {
|
||||
__rtic_internal_p: ::core::marker::PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
#(#cfgs)*
|
||||
impl<'a> #shared_name<'a> {
|
||||
#[inline(always)]
|
||||
pub unsafe fn new() -> Self {
|
||||
#shared_name { __rtic_internal_p: ::core::marker::PhantomData }
|
||||
}
|
||||
}
|
||||
));
|
||||
|
||||
let ptr = quote!(
|
||||
#(#cfgs)*
|
||||
#mangled_name.get_mut() as *mut _
|
||||
);
|
||||
|
||||
let ceiling = match analysis.ownerships.get(name) {
|
||||
Some(Ownership::Owned { priority } | Ownership::CoOwned { priority }) => *priority,
|
||||
Some(Ownership::Contended { ceiling }) => *ceiling,
|
||||
None => 0,
|
||||
};
|
||||
|
||||
// For future use
|
||||
// let doc = format!(" RTIC internal ({} resource): {}:{}", doc, file!(), line!());
|
||||
|
||||
mod_app.push(util::impl_mutex(
|
||||
app,
|
||||
cfgs,
|
||||
true,
|
||||
&shared_name,
|
||||
"e!(#ty),
|
||||
ceiling,
|
||||
&ptr,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let mod_resources = if mod_resources.is_empty() {
|
||||
quote!()
|
||||
} else {
|
||||
quote!(mod shared_resources {
|
||||
#(#mod_resources)*
|
||||
})
|
||||
};
|
||||
|
||||
// Computing mapping of used interrupts to masks
|
||||
let interrupt_ids = analysis.interrupts.iter().map(|(p, (id, _))| (p, id));
|
||||
|
||||
let mut prio_to_masks = HashMap::new();
|
||||
let device = &app.args.device;
|
||||
let mut uses_exceptions_with_resources = false;
|
||||
|
||||
let mut mask_ids = Vec::new();
|
||||
|
||||
for (&priority, name) in interrupt_ids.chain(app.hardware_tasks.values().flat_map(|task| {
|
||||
if !util::is_exception(&task.args.binds) {
|
||||
Some((&task.args.priority, &task.args.binds))
|
||||
} else {
|
||||
// If any resource to the exception uses non-lock-free or non-local resources this is
|
||||
// not allwed on thumbv6.
|
||||
uses_exceptions_with_resources = uses_exceptions_with_resources
|
||||
|| task
|
||||
.args
|
||||
.shared_resources
|
||||
.iter()
|
||||
.map(|(ident, access)| {
|
||||
if access.is_exclusive() {
|
||||
if let Some(r) = app.shared_resources.get(ident) {
|
||||
!r.properties.lock_free
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.any(|v| v);
|
||||
|
||||
None
|
||||
}
|
||||
})) {
|
||||
let v: &mut Vec<_> = prio_to_masks.entry(priority - 1).or_default();
|
||||
v.push(quote!(#device::Interrupt::#name as u32));
|
||||
mask_ids.push(quote!(#device::Interrupt::#name as u32));
|
||||
}
|
||||
|
||||
// Call rtic::export::create_mask([Mask; N]), where the array is the list of shifts
|
||||
|
||||
let mut mask_arr = Vec::new();
|
||||
// NOTE: 0..3 assumes max 4 priority levels according to M0, M23 spec
|
||||
for i in 0..3 {
|
||||
let v = if let Some(v) = prio_to_masks.get(&i) {
|
||||
v.clone()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
mask_arr.push(quote!(
|
||||
rtic::export::create_mask([#(#v),*])
|
||||
));
|
||||
}
|
||||
|
||||
// Generate a constant for the number of chunks needed by Mask.
|
||||
let chunks_name = util::priority_mask_chunks_ident();
|
||||
mod_app.push(quote!(
|
||||
#[doc(hidden)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
const #chunks_name: usize = rtic::export::compute_mask_chunks([#(#mask_ids),*]);
|
||||
));
|
||||
|
||||
let masks_name = util::priority_masks_ident();
|
||||
mod_app.push(quote!(
|
||||
#[doc(hidden)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
const #masks_name: [rtic::export::Mask<#chunks_name>; 3] = [#(#mask_arr),*];
|
||||
));
|
||||
|
||||
if uses_exceptions_with_resources {
|
||||
mod_app.push(quote!(
|
||||
#[doc(hidden)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
const __rtic_internal_V6_ERROR: () = rtic::export::no_basepri_panic();
|
||||
));
|
||||
}
|
||||
|
||||
quote!(
|
||||
#(#mod_app)*
|
||||
|
||||
#mod_resources
|
||||
)
|
||||
}
|
||||
119
rtic-macros/src/codegen/shared_resources_struct.rs
Normal file
119
rtic-macros/src/codegen/shared_resources_struct.rs
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
use crate::syntax::{ast::App, Context};
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
|
||||
use crate::codegen::util;
|
||||
|
||||
/// Generate shared resources structs
|
||||
pub fn codegen(ctxt: Context, app: &App) -> (TokenStream2, TokenStream2) {
|
||||
let resources = match ctxt {
|
||||
Context::Init => unreachable!("Tried to generate shared resources struct for init"),
|
||||
Context::Idle => {
|
||||
&app.idle
|
||||
.as_ref()
|
||||
.expect("RTIC-ICE: unable to get idle name")
|
||||
.args
|
||||
.shared_resources
|
||||
}
|
||||
Context::HardwareTask(name) => &app.hardware_tasks[name].args.shared_resources,
|
||||
Context::SoftwareTask(name) => &app.software_tasks[name].args.shared_resources,
|
||||
};
|
||||
|
||||
let mut fields = vec![];
|
||||
let mut values = vec![];
|
||||
|
||||
for (name, access) in resources {
|
||||
let res = app.shared_resources.get(name).expect("UNREACHABLE");
|
||||
|
||||
let cfgs = &res.cfgs;
|
||||
|
||||
// access hold if the resource is [x] (exclusive) or [&x] (shared)
|
||||
let mut_ = if access.is_exclusive() {
|
||||
Some(quote!(mut))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let ty = &res.ty;
|
||||
let mangled_name = util::static_shared_resource_ident(name);
|
||||
let shared_name = util::need_to_lock_ident(name);
|
||||
|
||||
if res.properties.lock_free {
|
||||
// Lock free resources of `idle` and `init` get 'static lifetime
|
||||
let lt = if ctxt.runs_once() {
|
||||
quote!('static)
|
||||
} else {
|
||||
quote!('a)
|
||||
};
|
||||
|
||||
fields.push(quote!(
|
||||
#(#cfgs)*
|
||||
#[allow(missing_docs)]
|
||||
pub #name: &#lt #mut_ #ty
|
||||
));
|
||||
} else if access.is_shared() {
|
||||
fields.push(quote!(
|
||||
#(#cfgs)*
|
||||
#[allow(missing_docs)]
|
||||
pub #name: &'a #ty
|
||||
));
|
||||
} else {
|
||||
fields.push(quote!(
|
||||
#(#cfgs)*
|
||||
#[allow(missing_docs)]
|
||||
pub #name: shared_resources::#shared_name<'a>
|
||||
));
|
||||
|
||||
values.push(quote!(
|
||||
#(#cfgs)*
|
||||
#name: shared_resources::#shared_name::new()
|
||||
|
||||
));
|
||||
|
||||
// continue as the value has been filled,
|
||||
continue;
|
||||
}
|
||||
|
||||
let expr = if access.is_exclusive() {
|
||||
quote!(&mut *(&mut *#mangled_name.get_mut()).as_mut_ptr())
|
||||
} else {
|
||||
quote!(&*(&*#mangled_name.get()).as_ptr())
|
||||
};
|
||||
|
||||
values.push(quote!(
|
||||
#(#cfgs)*
|
||||
#name: #expr
|
||||
));
|
||||
}
|
||||
|
||||
fields.push(quote!(
|
||||
#[doc(hidden)]
|
||||
pub __rtic_internal_marker: core::marker::PhantomData<&'a ()>
|
||||
));
|
||||
|
||||
values.push(quote!(__rtic_internal_marker: core::marker::PhantomData));
|
||||
|
||||
let doc = format!("Shared resources `{}` has access to", ctxt.ident(app));
|
||||
let ident = util::shared_resources_ident(ctxt, app);
|
||||
let item = quote!(
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[doc = #doc]
|
||||
pub struct #ident<'a> {
|
||||
#(#fields,)*
|
||||
}
|
||||
);
|
||||
|
||||
let constructor = quote!(
|
||||
impl<'a> #ident<'a> {
|
||||
#[inline(always)]
|
||||
#[allow(missing_docs)]
|
||||
pub unsafe fn new() -> Self {
|
||||
#ident {
|
||||
#(#values,)*
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
(item, constructor)
|
||||
}
|
||||
64
rtic-macros/src/codegen/software_tasks.rs
Normal file
64
rtic-macros/src/codegen/software_tasks.rs
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
use crate::syntax::{ast::App, Context};
|
||||
use crate::{
|
||||
analyze::Analysis,
|
||||
codegen::{local_resources_struct, module, shared_resources_struct},
|
||||
};
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
|
||||
pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 {
|
||||
let mut mod_app = vec![];
|
||||
let mut root = vec![];
|
||||
let mut user_tasks = vec![];
|
||||
|
||||
// Any task
|
||||
for (name, task) in app.software_tasks.iter() {
|
||||
if !task.args.local_resources.is_empty() {
|
||||
let (item, constructor) =
|
||||
local_resources_struct::codegen(Context::SoftwareTask(name), app);
|
||||
|
||||
root.push(item);
|
||||
|
||||
mod_app.push(constructor);
|
||||
}
|
||||
|
||||
if !task.args.shared_resources.is_empty() {
|
||||
let (item, constructor) =
|
||||
shared_resources_struct::codegen(Context::SoftwareTask(name), app);
|
||||
|
||||
root.push(item);
|
||||
|
||||
mod_app.push(constructor);
|
||||
}
|
||||
|
||||
if !&task.is_extern {
|
||||
let context = &task.context;
|
||||
let attrs = &task.attrs;
|
||||
let cfgs = &task.cfgs;
|
||||
let stmts = &task.stmts;
|
||||
let inputs = &task.inputs;
|
||||
|
||||
user_tasks.push(quote!(
|
||||
#(#attrs)*
|
||||
#(#cfgs)*
|
||||
#[allow(non_snake_case)]
|
||||
async fn #name<'a>(#context: #name::Context<'a> #(,#inputs)*) {
|
||||
use rtic::Mutex as _;
|
||||
use rtic::mutex::prelude::*;
|
||||
|
||||
#(#stmts)*
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
root.push(module::codegen(Context::SoftwareTask(name), app, analysis));
|
||||
}
|
||||
|
||||
quote!(
|
||||
#(#mod_app)*
|
||||
|
||||
#(#root)*
|
||||
|
||||
#(#user_tasks)*
|
||||
)
|
||||
}
|
||||
238
rtic-macros/src/codegen/util.rs
Normal file
238
rtic-macros/src/codegen/util.rs
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
use crate::syntax::{ast::App, Context};
|
||||
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||
use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||
use quote::quote;
|
||||
use syn::{Attribute, Ident, PatType};
|
||||
|
||||
const RTIC_INTERNAL: &str = "__rtic_internal";
|
||||
|
||||
/// Generates a `Mutex` implementation
|
||||
pub fn impl_mutex(
|
||||
app: &App,
|
||||
cfgs: &[Attribute],
|
||||
resources_prefix: bool,
|
||||
name: &Ident,
|
||||
ty: &TokenStream2,
|
||||
ceiling: u8,
|
||||
ptr: &TokenStream2,
|
||||
) -> TokenStream2 {
|
||||
let path = if resources_prefix {
|
||||
quote!(shared_resources::#name)
|
||||
} else {
|
||||
quote!(#name)
|
||||
};
|
||||
|
||||
let device = &app.args.device;
|
||||
let masks_name = priority_masks_ident();
|
||||
quote!(
|
||||
#(#cfgs)*
|
||||
impl<'a> rtic::Mutex for #path<'a> {
|
||||
type T = #ty;
|
||||
|
||||
#[inline(always)]
|
||||
fn lock<RTIC_INTERNAL_R>(&mut self, f: impl FnOnce(&mut #ty) -> RTIC_INTERNAL_R) -> RTIC_INTERNAL_R {
|
||||
/// Priority ceiling
|
||||
const CEILING: u8 = #ceiling;
|
||||
|
||||
unsafe {
|
||||
rtic::export::lock(
|
||||
#ptr,
|
||||
CEILING,
|
||||
#device::NVIC_PRIO_BITS,
|
||||
&#masks_name,
|
||||
f,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
pub fn interrupt_ident() -> Ident {
|
||||
let span = Span::call_site();
|
||||
Ident::new("interrupt", span)
|
||||
}
|
||||
|
||||
/// Whether `name` is an exception with configurable priority
|
||||
pub fn is_exception(name: &Ident) -> bool {
|
||||
let s = name.to_string();
|
||||
|
||||
matches!(
|
||||
&*s,
|
||||
"MemoryManagement"
|
||||
| "BusFault"
|
||||
| "UsageFault"
|
||||
| "SecureFault"
|
||||
| "SVCall"
|
||||
| "DebugMonitor"
|
||||
| "PendSV"
|
||||
| "SysTick"
|
||||
)
|
||||
}
|
||||
|
||||
/// Mark a name as internal
|
||||
pub fn mark_internal_name(name: &str) -> Ident {
|
||||
Ident::new(&format!("{RTIC_INTERNAL}_{name}"), Span::call_site())
|
||||
}
|
||||
|
||||
/// Generate an internal identifier for tasks
|
||||
pub fn internal_task_ident(task: &Ident, ident_name: &str) -> Ident {
|
||||
mark_internal_name(&format!("{task}_{ident_name}"))
|
||||
}
|
||||
|
||||
fn link_section_index() -> usize {
|
||||
static INDEX: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
INDEX.fetch_add(1, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Add `link_section` attribute
|
||||
pub fn link_section_uninit() -> TokenStream2 {
|
||||
let section = format!(".uninit.rtic{}", link_section_index());
|
||||
|
||||
quote!(#[link_section = #section])
|
||||
}
|
||||
|
||||
/// Regroups the inputs of a task
|
||||
///
|
||||
/// `inputs` could be &[`input: Foo`] OR &[`mut x: i32`, `ref y: i64`]
|
||||
pub fn regroup_inputs(
|
||||
inputs: &[PatType],
|
||||
) -> (
|
||||
// args e.g. &[`_0`], &[`_0: i32`, `_1: i64`]
|
||||
Vec<TokenStream2>,
|
||||
// tupled e.g. `_0`, `(_0, _1)`
|
||||
TokenStream2,
|
||||
// untupled e.g. &[`_0`], &[`_0`, `_1`]
|
||||
Vec<TokenStream2>,
|
||||
// ty e.g. `Foo`, `(i32, i64)`
|
||||
TokenStream2,
|
||||
) {
|
||||
if inputs.len() == 1 {
|
||||
let ty = &inputs[0].ty;
|
||||
|
||||
(
|
||||
vec![quote!(_0: #ty)],
|
||||
quote!(_0),
|
||||
vec![quote!(_0)],
|
||||
quote!(#ty),
|
||||
)
|
||||
} else {
|
||||
let mut args = vec![];
|
||||
let mut pats = vec![];
|
||||
let mut tys = vec![];
|
||||
|
||||
for (i, input) in inputs.iter().enumerate() {
|
||||
let i = Ident::new(&format!("_{i}"), Span::call_site());
|
||||
let ty = &input.ty;
|
||||
|
||||
args.push(quote!(#i: #ty));
|
||||
|
||||
pats.push(quote!(#i));
|
||||
|
||||
tys.push(quote!(#ty));
|
||||
}
|
||||
|
||||
let tupled = {
|
||||
let pats = pats.clone();
|
||||
quote!((#(#pats,)*))
|
||||
};
|
||||
let ty = quote!((#(#tys,)*));
|
||||
(args, tupled, pats, ty)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the ident for the name of the task
|
||||
pub fn get_task_name(ctxt: Context, app: &App) -> Ident {
|
||||
let s = match ctxt {
|
||||
Context::Init => app.init.name.to_string(),
|
||||
Context::Idle => app
|
||||
.idle
|
||||
.as_ref()
|
||||
.expect("RTIC-ICE: unable to find idle name")
|
||||
.name
|
||||
.to_string(),
|
||||
Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(),
|
||||
};
|
||||
|
||||
Ident::new(&s, Span::call_site())
|
||||
}
|
||||
|
||||
/// Generates a pre-reexport identifier for the "shared resources" struct
|
||||
pub fn shared_resources_ident(ctxt: Context, app: &App) -> Ident {
|
||||
let mut s = match ctxt {
|
||||
Context::Init => app.init.name.to_string(),
|
||||
Context::Idle => app
|
||||
.idle
|
||||
.as_ref()
|
||||
.expect("RTIC-ICE: unable to find idle name")
|
||||
.name
|
||||
.to_string(),
|
||||
Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(),
|
||||
};
|
||||
|
||||
s.push_str("SharedResources");
|
||||
|
||||
mark_internal_name(&s)
|
||||
}
|
||||
|
||||
/// Generates a pre-reexport identifier for the "local resources" struct
|
||||
pub fn local_resources_ident(ctxt: Context, app: &App) -> Ident {
|
||||
let mut s = match ctxt {
|
||||
Context::Init => app.init.name.to_string(),
|
||||
Context::Idle => app
|
||||
.idle
|
||||
.as_ref()
|
||||
.expect("RTIC-ICE: unable to find idle name")
|
||||
.name
|
||||
.to_string(),
|
||||
Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(),
|
||||
};
|
||||
|
||||
s.push_str("LocalResources");
|
||||
|
||||
mark_internal_name(&s)
|
||||
}
|
||||
|
||||
/// Suffixed identifier
|
||||
pub fn suffixed(name: &str) -> Ident {
|
||||
let span = Span::call_site();
|
||||
Ident::new(name, span)
|
||||
}
|
||||
|
||||
pub fn static_shared_resource_ident(name: &Ident) -> Ident {
|
||||
mark_internal_name(&format!("shared_resource_{name}"))
|
||||
}
|
||||
|
||||
/// Generates an Ident for the number of 32 bit chunks used for Mask storage.
|
||||
pub fn priority_mask_chunks_ident() -> Ident {
|
||||
mark_internal_name("MASK_CHUNKS")
|
||||
}
|
||||
|
||||
pub fn priority_masks_ident() -> Ident {
|
||||
mark_internal_name("MASKS")
|
||||
}
|
||||
|
||||
pub fn static_local_resource_ident(name: &Ident) -> Ident {
|
||||
mark_internal_name(&format!("local_resource_{name}"))
|
||||
}
|
||||
|
||||
pub fn declared_static_local_resource_ident(name: &Ident, task_name: &Ident) -> Ident {
|
||||
mark_internal_name(&format!("local_{task_name}_{name}"))
|
||||
}
|
||||
|
||||
pub fn need_to_lock_ident(name: &Ident) -> Ident {
|
||||
Ident::new(&format!("{name}_that_needs_to_be_locked"), name.span())
|
||||
}
|
||||
|
||||
pub fn zero_prio_dispatcher_ident() -> Ident {
|
||||
Ident::new("__rtic_internal_async_0_prio_dispatcher", Span::call_site())
|
||||
}
|
||||
|
||||
/// The name to get better RT flag errors
|
||||
pub fn rt_err_ident() -> Ident {
|
||||
Ident::new(
|
||||
"you_must_enable_the_rt_feature_for_the_pac_in_your_cargo_toml",
|
||||
Span::call_site(),
|
||||
)
|
||||
}
|
||||
91
rtic-macros/src/lib.rs
Normal file
91
rtic-macros/src/lib.rs
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
#![doc(
|
||||
html_logo_url = "https://raw.githubusercontent.com/rtic-rs/rtic/master/book/en/src/RTIC.svg",
|
||||
html_favicon_url = "https://raw.githubusercontent.com/rtic-rs/rtic/master/book/en/src/RTIC.svg"
|
||||
)]
|
||||
//deny_warnings_placeholder_for_ci
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use std::{env, fs, path::Path};
|
||||
|
||||
mod analyze;
|
||||
mod bindings;
|
||||
mod check;
|
||||
mod codegen;
|
||||
mod syntax;
|
||||
|
||||
// Used for mocking the API in testing
|
||||
#[doc(hidden)]
|
||||
#[proc_macro_attribute]
|
||||
pub fn mock_app(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
if let Err(e) = syntax::parse(args, input) {
|
||||
e.to_compile_error().into()
|
||||
} else {
|
||||
"fn main() {}".parse().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Attribute used to declare a RTIC application
|
||||
///
|
||||
/// For user documentation see the [RTIC book](https://rtic.rs)
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Should never panic, cargo feeds a path which is later converted to a string
|
||||
#[proc_macro_attribute]
|
||||
pub fn app(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let (app, analysis) = match syntax::parse(args, input) {
|
||||
Err(e) => return e.to_compile_error().into(),
|
||||
Ok(x) => x,
|
||||
};
|
||||
|
||||
if let Err(e) = check::app(&app) {
|
||||
return e.to_compile_error().into();
|
||||
}
|
||||
|
||||
let analysis = analyze::app(analysis, &app);
|
||||
|
||||
let ts = codegen::app(&app, &analysis);
|
||||
|
||||
// Default output path: <project_dir>/target/
|
||||
let mut out_dir = Path::new("target");
|
||||
|
||||
// Get output directory from Cargo environment
|
||||
// TODO don't want to break builds if OUT_DIR is not set, is this ever the case?
|
||||
let out_str = env::var("OUT_DIR").unwrap_or_else(|_| "".to_string());
|
||||
|
||||
if !out_dir.exists() {
|
||||
// Set out_dir to OUT_DIR
|
||||
out_dir = Path::new(&out_str);
|
||||
|
||||
// Default build path, annotated below:
|
||||
// $(pwd)/target/thumbv7em-none-eabihf/debug/build/rtic-<HASH>/out/
|
||||
// <project_dir>/<target-dir>/<TARGET>/debug/build/rtic-<HASH>/out/
|
||||
//
|
||||
// traverse up to first occurrence of TARGET, approximated with starts_with("thumbv")
|
||||
// and use the parent() of this path
|
||||
//
|
||||
// If no "target" directory is found, <project_dir>/<out_dir_root> is used
|
||||
for path in out_dir.ancestors() {
|
||||
if let Some(dir) = path.components().last() {
|
||||
let dir = dir.as_os_str().to_str().unwrap();
|
||||
|
||||
if dir.starts_with("thumbv") || dir.starts_with("riscv") {
|
||||
if let Some(out) = path.parent() {
|
||||
out_dir = out;
|
||||
break;
|
||||
}
|
||||
// If no parent, just use it
|
||||
out_dir = path;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to write the expanded code to disk
|
||||
if let Some(out_str) = out_dir.to_str() {
|
||||
fs::write(format!("{out_str}/rtic-expansion.rs"), ts.to_string()).ok();
|
||||
}
|
||||
|
||||
ts.into()
|
||||
}
|
||||
121
rtic-macros/src/syntax.rs
Normal file
121
rtic-macros/src/syntax.rs
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
#[allow(unused_extern_crates)]
|
||||
extern crate proc_macro;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
|
||||
use indexmap::{IndexMap, IndexSet};
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use syn::Ident;
|
||||
|
||||
use crate::syntax::ast::App;
|
||||
|
||||
mod accessors;
|
||||
pub mod analyze;
|
||||
pub mod ast;
|
||||
mod check;
|
||||
mod parse;
|
||||
|
||||
/// An ordered map keyed by identifier
|
||||
pub type Map<T> = IndexMap<Ident, T>;
|
||||
|
||||
/// An order set
|
||||
pub type Set<T> = IndexSet<T>;
|
||||
|
||||
/// Execution context
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Context<'a> {
|
||||
/// The `idle` context
|
||||
Idle,
|
||||
|
||||
/// The `init`-ialization function
|
||||
Init,
|
||||
|
||||
/// A async software task
|
||||
SoftwareTask(&'a Ident),
|
||||
|
||||
/// A hardware task
|
||||
HardwareTask(&'a Ident),
|
||||
}
|
||||
|
||||
impl<'a> Context<'a> {
|
||||
/// The identifier of this context
|
||||
pub fn ident(&self, app: &'a App) -> &'a Ident {
|
||||
match self {
|
||||
Context::HardwareTask(ident) => ident,
|
||||
Context::Idle => &app.idle.as_ref().unwrap().name,
|
||||
Context::Init => &app.init.name,
|
||||
Context::SoftwareTask(ident) => ident,
|
||||
}
|
||||
}
|
||||
|
||||
/// Is this the `idle` context?
|
||||
pub fn is_idle(&self) -> bool {
|
||||
matches!(self, Context::Idle)
|
||||
}
|
||||
|
||||
/// Is this the `init`-ialization context?
|
||||
pub fn is_init(&self) -> bool {
|
||||
matches!(self, Context::Init)
|
||||
}
|
||||
|
||||
/// Whether this context runs only once
|
||||
pub fn runs_once(&self) -> bool {
|
||||
self.is_init() || self.is_idle()
|
||||
}
|
||||
|
||||
/// Whether this context has shared resources
|
||||
pub fn has_shared_resources(&self, app: &App) -> bool {
|
||||
match *self {
|
||||
Context::HardwareTask(name) => {
|
||||
!app.hardware_tasks[name].args.shared_resources.is_empty()
|
||||
}
|
||||
Context::Idle => !app.idle.as_ref().unwrap().args.shared_resources.is_empty(),
|
||||
Context::Init => false,
|
||||
Context::SoftwareTask(name) => {
|
||||
!app.software_tasks[name].args.shared_resources.is_empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether this context has local resources
|
||||
pub fn has_local_resources(&self, app: &App) -> bool {
|
||||
match *self {
|
||||
Context::HardwareTask(name) => {
|
||||
!app.hardware_tasks[name].args.local_resources.is_empty()
|
||||
}
|
||||
Context::Idle => !app.idle.as_ref().unwrap().args.local_resources.is_empty(),
|
||||
Context::Init => !app.init.args.local_resources.is_empty(),
|
||||
Context::SoftwareTask(name) => {
|
||||
!app.software_tasks[name].args.local_resources.is_empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses the input of the `#[app]` attribute
|
||||
pub fn parse(
|
||||
args: TokenStream,
|
||||
input: TokenStream,
|
||||
) -> Result<(ast::App, analyze::Analysis), syn::parse::Error> {
|
||||
parse2(args.into(), input.into())
|
||||
}
|
||||
|
||||
/// `proc_macro2::TokenStream` version of `parse`
|
||||
pub fn parse2(
|
||||
args: TokenStream2,
|
||||
input: TokenStream2,
|
||||
) -> Result<(ast::App, analyze::Analysis), syn::parse::Error> {
|
||||
let app = parse::app(args, input)?;
|
||||
check::app(&app)?;
|
||||
|
||||
match analyze::app(&app) {
|
||||
Err(e) => Err(e),
|
||||
// If no errors, return the app and analysis results
|
||||
Ok(analysis) => Ok((app, analysis)),
|
||||
}
|
||||
}
|
||||
|
||||
enum Either<A, B> {
|
||||
Left(A),
|
||||
Right(B),
|
||||
}
|
||||
31
rtic-macros/src/syntax/.travis.yml
Normal file
31
rtic-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
rtic-macros/src/syntax/accessors.rs
Normal file
113
rtic-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()
|
||||
}
|
||||
}
|
||||
414
rtic-macros/src/syntax/analyze.rs
Normal file
414
rtic-macros/src/syntax/analyze.rs
Normal file
|
|
@ -0,0 +1,414 @@
|
|||
//! RTIC application analysis
|
||||
|
||||
use core::cmp;
|
||||
use std::collections::{BTreeMap, BTreeSet, HashMap};
|
||||
|
||||
use indexmap::{IndexMap, IndexSet};
|
||||
use syn::{Ident, Type};
|
||||
|
||||
use crate::syntax::{
|
||||
ast::{App, LocalResources, TaskLocal},
|
||||
Set,
|
||||
};
|
||||
|
||||
pub(crate) fn app(app: &App) -> Result<Analysis, syn::Error> {
|
||||
// Collect all tasks into a vector
|
||||
type TaskName = Ident;
|
||||
type Priority = u8;
|
||||
|
||||
// The task list is a Tuple (Name, Shared Resources, Local Resources, Priority)
|
||||
let task_resources_list: Vec<(TaskName, Vec<&Ident>, &LocalResources, Priority)> =
|
||||
Some(&app.init)
|
||||
.iter()
|
||||
.map(|ht| (ht.name.clone(), Vec::new(), &ht.args.local_resources, 0))
|
||||
.chain(app.idle.iter().map(|ht| {
|
||||
(
|
||||
ht.name.clone(),
|
||||
ht.args
|
||||
.shared_resources
|
||||
.iter()
|
||||
.map(|(v, _)| v)
|
||||
.collect::<Vec<_>>(),
|
||||
&ht.args.local_resources,
|
||||
0,
|
||||
)
|
||||
}))
|
||||
.chain(app.software_tasks.iter().map(|(name, ht)| {
|
||||
(
|
||||
name.clone(),
|
||||
ht.args
|
||||
.shared_resources
|
||||
.iter()
|
||||
.map(|(v, _)| v)
|
||||
.collect::<Vec<_>>(),
|
||||
&ht.args.local_resources,
|
||||
ht.args.priority,
|
||||
)
|
||||
}))
|
||||
.chain(app.hardware_tasks.iter().map(|(name, ht)| {
|
||||
(
|
||||
name.clone(),
|
||||
ht.args
|
||||
.shared_resources
|
||||
.iter()
|
||||
.map(|(v, _)| v)
|
||||
.collect::<Vec<_>>(),
|
||||
&ht.args.local_resources,
|
||||
ht.args.priority,
|
||||
)
|
||||
}))
|
||||
.collect();
|
||||
|
||||
let mut error = vec![];
|
||||
let mut lf_res_with_error = vec![];
|
||||
let mut lf_hash = HashMap::new();
|
||||
|
||||
// Collect lock free resources
|
||||
let lock_free: Vec<&Ident> = app
|
||||
.shared_resources
|
||||
.iter()
|
||||
.filter(|(_, r)| r.properties.lock_free)
|
||||
.map(|(i, _)| i)
|
||||
.collect();
|
||||
|
||||
// Check that lock_free resources are correct
|
||||
for lf_res in lock_free.iter() {
|
||||
for (task, tr, _, priority) in task_resources_list.iter() {
|
||||
for r in tr {
|
||||
// Get all uses of resources annotated lock_free
|
||||
if lf_res == r {
|
||||
// Check so async tasks do not use lock free resources
|
||||
if app.software_tasks.get(task).is_some() {
|
||||
error.push(syn::Error::new(
|
||||
r.span(),
|
||||
format!(
|
||||
"Lock free shared resource {:?} is used by an async tasks, which is forbidden",
|
||||
r.to_string(),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
// HashMap returns the previous existing object if old.key == new.key
|
||||
if let Some(lf_res) = lf_hash.insert(r.to_string(), (task, r, priority)) {
|
||||
// Check if priority differ, if it does, append to
|
||||
// list of resources which will be annotated with errors
|
||||
if priority != lf_res.2 {
|
||||
lf_res_with_error.push(lf_res.1);
|
||||
lf_res_with_error.push(r);
|
||||
}
|
||||
|
||||
// If the resource already violates lock free properties
|
||||
if lf_res_with_error.contains(&r) {
|
||||
lf_res_with_error.push(lf_res.1);
|
||||
lf_res_with_error.push(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add error message in the resource struct
|
||||
for r in lock_free {
|
||||
if lf_res_with_error.contains(&&r) {
|
||||
error.push(syn::Error::new(
|
||||
r.span(),
|
||||
format!(
|
||||
"Lock free shared resource {:?} is used by tasks at different priorities",
|
||||
r.to_string(),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Add error message for each use of the shared resource
|
||||
for resource in lf_res_with_error.clone() {
|
||||
error.push(syn::Error::new(
|
||||
resource.span(),
|
||||
format!(
|
||||
"Shared resource {:?} is declared lock free but used by tasks at different priorities",
|
||||
resource.to_string(),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
// Collect local resources
|
||||
let local: Vec<&Ident> = app.local_resources.iter().map(|(i, _)| i).collect();
|
||||
|
||||
let mut lr_with_error = vec![];
|
||||
let mut lr_hash = HashMap::new();
|
||||
|
||||
// Check that local resources are not shared
|
||||
for lr in local {
|
||||
for (task, _, local_resources, _) in task_resources_list.iter() {
|
||||
for (name, res) in local_resources.iter() {
|
||||
// Get all uses of resources annotated lock_free
|
||||
if lr == name {
|
||||
match res {
|
||||
TaskLocal::External => {
|
||||
// HashMap returns the previous existing object if old.key == new.key
|
||||
if let Some(lr) = lr_hash.insert(name.to_string(), (task, name)) {
|
||||
lr_with_error.push(lr.1);
|
||||
lr_with_error.push(name);
|
||||
}
|
||||
}
|
||||
// If a declared local has the same name as the `#[local]` struct, it's an
|
||||
// direct error
|
||||
TaskLocal::Declared(_) => {
|
||||
lr_with_error.push(lr);
|
||||
lr_with_error.push(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add error message for each use of the local resource
|
||||
for resource in lr_with_error.clone() {
|
||||
error.push(syn::Error::new(
|
||||
resource.span(),
|
||||
format!(
|
||||
"Local resource {:?} is used by multiple tasks or collides with multiple definitions",
|
||||
resource.to_string(),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
// Check 0-priority async software tasks and idle dependency
|
||||
for (name, task) in &app.software_tasks {
|
||||
if task.args.priority == 0 {
|
||||
// If there is a 0-priority task, there must be no idle
|
||||
if app.idle.is_some() {
|
||||
error.push(syn::Error::new(
|
||||
name.span(),
|
||||
format!(
|
||||
"Async task {:?} has priority 0, but `#[idle]` is defined. 0-priority async tasks are only allowed if there is no `#[idle]`.",
|
||||
name.to_string(),
|
||||
)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collect errors if any and return/halt
|
||||
if !error.is_empty() {
|
||||
let mut err = error.get(0).unwrap().clone();
|
||||
error.iter().for_each(|e| err.combine(e.clone()));
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
// e. Location of resources
|
||||
let mut used_shared_resource = IndexSet::new();
|
||||
let mut ownerships = Ownerships::new();
|
||||
let mut sync_types = SyncTypes::new();
|
||||
for (prio, name, access) in app.shared_resource_accesses() {
|
||||
let res = app.shared_resources.get(name).expect("UNREACHABLE");
|
||||
|
||||
// (e)
|
||||
// This shared resource is used
|
||||
used_shared_resource.insert(name.clone());
|
||||
|
||||
// (c)
|
||||
if let Some(priority) = prio {
|
||||
if let Some(ownership) = ownerships.get_mut(name) {
|
||||
match *ownership {
|
||||
Ownership::Owned { priority: ceiling }
|
||||
| Ownership::CoOwned { priority: ceiling }
|
||||
| Ownership::Contended { ceiling }
|
||||
if priority != ceiling =>
|
||||
{
|
||||
*ownership = Ownership::Contended {
|
||||
ceiling: cmp::max(ceiling, priority),
|
||||
};
|
||||
|
||||
if access.is_shared() {
|
||||
sync_types.insert(res.ty.clone());
|
||||
}
|
||||
}
|
||||
|
||||
Ownership::Owned { priority: ceil } if ceil == priority => {
|
||||
*ownership = Ownership::CoOwned { priority };
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
ownerships.insert(name.clone(), Ownership::Owned { priority });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create the list of used local resource Idents
|
||||
let mut used_local_resource = IndexSet::new();
|
||||
|
||||
for (_, _, locals, _) in task_resources_list {
|
||||
for (local, _) in locals {
|
||||
used_local_resource.insert(local.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Most shared resources need to be `Send`, only 0 prio does not need it
|
||||
let mut send_types = SendTypes::new();
|
||||
|
||||
for (name, res) in app.shared_resources.iter() {
|
||||
if ownerships
|
||||
.get(name)
|
||||
.map(|ownership| match *ownership {
|
||||
Ownership::Owned { priority: ceiling }
|
||||
| Ownership::CoOwned { priority: ceiling }
|
||||
| Ownership::Contended { ceiling } => ceiling != 0,
|
||||
})
|
||||
.unwrap_or(false)
|
||||
{
|
||||
send_types.insert(res.ty.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Most local resources need to be `Send` as well, only 0 prio does not need it
|
||||
for (name, res) in app.local_resources.iter() {
|
||||
if ownerships
|
||||
.get(name)
|
||||
.map(|ownership| match *ownership {
|
||||
Ownership::Owned { priority: ceiling }
|
||||
| Ownership::CoOwned { priority: ceiling }
|
||||
| Ownership::Contended { ceiling } => ceiling != 0,
|
||||
})
|
||||
.unwrap_or(false)
|
||||
{
|
||||
send_types.insert(res.ty.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let mut channels = Channels::new();
|
||||
|
||||
for (name, spawnee) in &app.software_tasks {
|
||||
let spawnee_prio = spawnee.args.priority;
|
||||
|
||||
let channel = channels.entry(spawnee_prio).or_default();
|
||||
channel.tasks.insert(name.clone());
|
||||
|
||||
// All inputs are send as we do not know from where they may be spawned.
|
||||
spawnee.inputs.iter().for_each(|input| {
|
||||
send_types.insert(input.ty.clone());
|
||||
});
|
||||
}
|
||||
|
||||
// No channel should ever be empty
|
||||
debug_assert!(channels.values().all(|channel| !channel.tasks.is_empty()));
|
||||
|
||||
Ok(Analysis {
|
||||
channels,
|
||||
shared_resources: used_shared_resource,
|
||||
local_resources: used_local_resource,
|
||||
ownerships,
|
||||
send_types,
|
||||
sync_types,
|
||||
})
|
||||
}
|
||||
|
||||
// /// Priority ceiling
|
||||
// pub type Ceiling = Option<u8>;
|
||||
|
||||
/// Task priority
|
||||
pub type Priority = u8;
|
||||
|
||||
/// Resource name
|
||||
pub type Resource = Ident;
|
||||
|
||||
/// Task name
|
||||
pub type Task = Ident;
|
||||
|
||||
/// The result of analyzing an RTIC application
|
||||
pub struct Analysis {
|
||||
/// SPSC message channels
|
||||
pub channels: Channels,
|
||||
|
||||
/// Shared resources
|
||||
///
|
||||
/// If a resource is not listed here it means that's a "dead" (never
|
||||
/// accessed) resource and the backend should not generate code for it
|
||||
pub shared_resources: UsedSharedResource,
|
||||
|
||||
/// Local resources
|
||||
///
|
||||
/// If a resource is not listed here it means that's a "dead" (never
|
||||
/// accessed) resource and the backend should not generate code for it
|
||||
pub local_resources: UsedLocalResource,
|
||||
|
||||
/// Resource ownership
|
||||
pub ownerships: Ownerships,
|
||||
|
||||
/// These types must implement the `Send` trait
|
||||
pub send_types: SendTypes,
|
||||
|
||||
/// These types must implement the `Sync` trait
|
||||
pub sync_types: SyncTypes,
|
||||
}
|
||||
|
||||
/// All channels, keyed by dispatch priority
|
||||
pub type Channels = BTreeMap<Priority, Channel>;
|
||||
|
||||
/// Location of all *used* shared resources
|
||||
pub type UsedSharedResource = IndexSet<Resource>;
|
||||
|
||||
/// Location of all *used* local resources
|
||||
pub type UsedLocalResource = IndexSet<Resource>;
|
||||
|
||||
/// Resource ownership
|
||||
pub type Ownerships = IndexMap<Resource, Ownership>;
|
||||
|
||||
/// These types must implement the `Send` trait
|
||||
pub type SendTypes = Set<Box<Type>>;
|
||||
|
||||
/// These types must implement the `Sync` trait
|
||||
pub type SyncTypes = Set<Box<Type>>;
|
||||
|
||||
/// A channel used to send messages
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Channel {
|
||||
/// 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 { .. })
|
||||
// }
|
||||
// }
|
||||
335
rtic-macros/src/syntax/ast.rs
Normal file
335
rtic-macros/src/syntax/ast.rs
Normal file
|
|
@ -0,0 +1,335 @@
|
|||
//! Abstract Syntax Tree
|
||||
|
||||
use syn::{Attribute, Expr, Ident, Item, ItemUse, Pat, PatType, Path, Stmt, Type};
|
||||
|
||||
use crate::syntax::Map;
|
||||
|
||||
/// The `#[app]` attribute
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub struct App {
|
||||
/// The arguments to the `#[app]` attribute
|
||||
pub args: AppArgs,
|
||||
|
||||
/// The name of the `const` item on which the `#[app]` attribute has been placed
|
||||
pub name: Ident,
|
||||
|
||||
/// The `#[init]` function
|
||||
pub init: Init,
|
||||
|
||||
/// The `#[idle]` function
|
||||
pub idle: Option<Idle>,
|
||||
|
||||
/// Resources shared between tasks defined in `#[shared]`
|
||||
pub shared_resources: Map<SharedResource>,
|
||||
|
||||
/// Task local resources defined in `#[local]`
|
||||
pub local_resources: Map<LocalResource>,
|
||||
|
||||
/// User imports
|
||||
pub user_imports: Vec<ItemUse>,
|
||||
|
||||
/// User code
|
||||
pub user_code: Vec<Item>,
|
||||
|
||||
/// Hardware tasks: `#[task(binds = ..)]`s
|
||||
pub hardware_tasks: Map<HardwareTask>,
|
||||
|
||||
/// Async software tasks: `#[task]`
|
||||
pub software_tasks: Map<SoftwareTask>,
|
||||
}
|
||||
|
||||
/// Interrupts used to dispatch software tasks
|
||||
pub type Dispatchers = Map<Dispatcher>;
|
||||
|
||||
/// Interrupt that could be used to dispatch software tasks
|
||||
#[derive(Debug, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub struct Dispatcher {
|
||||
/// Attributes that will apply to this interrupt handler
|
||||
pub attrs: Vec<Attribute>,
|
||||
}
|
||||
|
||||
/// The arguments of the `#[app]` attribute
|
||||
#[derive(Debug)]
|
||||
pub struct AppArgs {
|
||||
/// Device
|
||||
pub device: Path,
|
||||
|
||||
/// Peripherals
|
||||
pub peripherals: bool,
|
||||
|
||||
/// Interrupts used to dispatch software tasks
|
||||
pub dispatchers: Dispatchers,
|
||||
}
|
||||
|
||||
/// The `init`-ialization function
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub struct Init {
|
||||
/// `init` context metadata
|
||||
pub args: InitArgs,
|
||||
|
||||
/// Attributes that will apply to this `init` function
|
||||
pub attrs: Vec<Attribute>,
|
||||
|
||||
/// The name of the `#[init]` function
|
||||
pub name: Ident,
|
||||
|
||||
/// The context argument
|
||||
pub context: Box<Pat>,
|
||||
|
||||
/// The statements that make up this `init` function
|
||||
pub stmts: Vec<Stmt>,
|
||||
|
||||
/// The name of the user provided shared resources struct
|
||||
pub user_shared_struct: Ident,
|
||||
|
||||
/// The name of the user provided local resources struct
|
||||
pub user_local_struct: Ident,
|
||||
}
|
||||
|
||||
/// `init` context metadata
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub struct InitArgs {
|
||||
/// Local resources that can be accessed from this context
|
||||
pub local_resources: LocalResources,
|
||||
}
|
||||
|
||||
impl Default for InitArgs {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
local_resources: LocalResources::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The `idle` context
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub struct Idle {
|
||||
/// `idle` context metadata
|
||||
pub args: IdleArgs,
|
||||
|
||||
/// Attributes that will apply to this `idle` function
|
||||
pub attrs: Vec<Attribute>,
|
||||
|
||||
/// The name of the `#[idle]` function
|
||||
pub name: Ident,
|
||||
|
||||
/// The context argument
|
||||
pub context: Box<Pat>,
|
||||
|
||||
/// The statements that make up this `idle` function
|
||||
pub stmts: Vec<Stmt>,
|
||||
}
|
||||
|
||||
/// `idle` context metadata
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub struct IdleArgs {
|
||||
/// Local resources that can be accessed from this context
|
||||
pub local_resources: LocalResources,
|
||||
|
||||
/// Shared resources that can be accessed from this context
|
||||
pub shared_resources: SharedResources,
|
||||
}
|
||||
|
||||
impl Default for IdleArgs {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
local_resources: LocalResources::new(),
|
||||
shared_resources: SharedResources::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Shared resource properties
|
||||
#[derive(Debug)]
|
||||
pub struct SharedResourceProperties {
|
||||
/// A lock free (exclusive resource)
|
||||
pub lock_free: bool,
|
||||
}
|
||||
|
||||
/// A shared resource, defined in `#[shared]`
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub struct SharedResource {
|
||||
/// `#[cfg]` attributes like `#[cfg(debug_assertions)]`
|
||||
pub cfgs: Vec<Attribute>,
|
||||
|
||||
/// `#[doc]` attributes like `/// this is a docstring`
|
||||
pub docs: Vec<Attribute>,
|
||||
|
||||
/// Attributes that will apply to this resource
|
||||
pub attrs: Vec<Attribute>,
|
||||
|
||||
/// The type of this resource
|
||||
pub ty: Box<Type>,
|
||||
|
||||
/// Shared resource properties
|
||||
pub properties: SharedResourceProperties,
|
||||
}
|
||||
|
||||
/// A local resource, defined in `#[local]`
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub struct LocalResource {
|
||||
/// `#[cfg]` attributes like `#[cfg(debug_assertions)]`
|
||||
pub cfgs: Vec<Attribute>,
|
||||
|
||||
/// `#[doc]` attributes like `/// this is a docstring`
|
||||
pub docs: Vec<Attribute>,
|
||||
|
||||
/// Attributes that will apply to this resource
|
||||
pub attrs: Vec<Attribute>,
|
||||
|
||||
/// The type of this resource
|
||||
pub ty: Box<Type>,
|
||||
}
|
||||
|
||||
/// An async software task
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub struct SoftwareTask {
|
||||
/// Software task metadata
|
||||
pub args: SoftwareTaskArgs,
|
||||
|
||||
/// `#[cfg]` attributes like `#[cfg(debug_assertions)]`
|
||||
pub cfgs: Vec<Attribute>,
|
||||
|
||||
/// Attributes that will apply to this interrupt handler
|
||||
pub attrs: Vec<Attribute>,
|
||||
|
||||
/// The context argument
|
||||
pub context: Box<Pat>,
|
||||
|
||||
/// The inputs of this software task
|
||||
pub inputs: Vec<PatType>,
|
||||
|
||||
/// The statements that make up the task handler
|
||||
pub stmts: Vec<Stmt>,
|
||||
|
||||
/// The task is declared externally
|
||||
pub is_extern: bool,
|
||||
}
|
||||
|
||||
/// Software task metadata
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub struct SoftwareTaskArgs {
|
||||
/// The priority of this task
|
||||
pub priority: u8,
|
||||
|
||||
/// Local resources that can be accessed from this context
|
||||
pub local_resources: LocalResources,
|
||||
|
||||
/// Shared resources that can be accessed from this context
|
||||
pub shared_resources: SharedResources,
|
||||
}
|
||||
|
||||
impl Default for SoftwareTaskArgs {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
priority: 1,
|
||||
local_resources: LocalResources::new(),
|
||||
shared_resources: SharedResources::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A hardware task
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub struct HardwareTask {
|
||||
/// Hardware task metadata
|
||||
pub args: HardwareTaskArgs,
|
||||
|
||||
/// `#[cfg]` attributes like `#[cfg(debug_assertions)]`
|
||||
pub cfgs: Vec<Attribute>,
|
||||
|
||||
/// Attributes that will apply to this interrupt handler
|
||||
pub attrs: Vec<Attribute>,
|
||||
|
||||
/// The context argument
|
||||
pub context: Box<Pat>,
|
||||
|
||||
/// The statements that make up the task handler
|
||||
pub stmts: Vec<Stmt>,
|
||||
|
||||
/// The task is declared externally
|
||||
pub is_extern: bool,
|
||||
}
|
||||
|
||||
/// Hardware task metadata
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub struct HardwareTaskArgs {
|
||||
/// The interrupt or exception that this task is bound to
|
||||
pub binds: Ident,
|
||||
|
||||
/// The priority of this task
|
||||
pub priority: u8,
|
||||
|
||||
/// Local resources that can be accessed from this context
|
||||
pub local_resources: LocalResources,
|
||||
|
||||
/// Shared resources that can be accessed from this context
|
||||
pub shared_resources: SharedResources,
|
||||
}
|
||||
|
||||
/// A `static mut` variable local to and owned by a context
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub struct Local {
|
||||
/// Attributes like `#[link_section]`
|
||||
pub attrs: Vec<Attribute>,
|
||||
|
||||
/// `#[cfg]` attributes like `#[cfg(debug_assertions)]`
|
||||
pub cfgs: Vec<Attribute>,
|
||||
|
||||
/// Type
|
||||
pub ty: Box<Type>,
|
||||
|
||||
/// Initial value
|
||||
pub expr: Box<Expr>,
|
||||
}
|
||||
|
||||
/// A wrapper of the 2 kinds of locals that tasks can have
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum TaskLocal {
|
||||
/// The local is declared externally (i.e. `#[local]` struct)
|
||||
External,
|
||||
/// The local is declared in the task
|
||||
Declared(Local),
|
||||
}
|
||||
|
||||
/// Resource access
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum Access {
|
||||
/// `[x]`, a mutable resource
|
||||
Exclusive,
|
||||
|
||||
/// `[&x]`, a static non-mutable resource
|
||||
Shared,
|
||||
}
|
||||
|
||||
impl Access {
|
||||
/// Is this enum in the `Exclusive` variant?
|
||||
pub fn is_exclusive(&self) -> bool {
|
||||
*self == Access::Exclusive
|
||||
}
|
||||
|
||||
/// Is this enum in the `Shared` variant?
|
||||
pub fn is_shared(&self) -> bool {
|
||||
*self == Access::Shared
|
||||
}
|
||||
}
|
||||
|
||||
/// Shared resource access list in task attribute
|
||||
pub type SharedResources = Map<Access>;
|
||||
|
||||
/// Local resource access/declaration list in task attribute
|
||||
pub type LocalResources = Map<TaskLocal>;
|
||||
66
rtic-macros/src/syntax/check.rs
Normal file
66
rtic-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
rtic-macros/src/syntax/optimize.rs
Normal file
36
rtic-macros/src/syntax/optimize.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
use std::collections::{BTreeSet, HashMap};
|
||||
|
||||
use crate::syntax::ast::App;
|
||||
|
||||
pub fn app(app: &mut App, settings: &Settings) {
|
||||
// "compress" priorities
|
||||
// If the user specified, for example, task priorities of "1, 3, 6",
|
||||
// compress them into "1, 2, 3" as to leave no gaps
|
||||
if settings.optimize_priorities {
|
||||
// all task priorities ordered in ascending order
|
||||
let priorities = app
|
||||
.hardware_tasks
|
||||
.values()
|
||||
.map(|task| Some(task.args.priority))
|
||||
.chain(
|
||||
app.software_tasks
|
||||
.values()
|
||||
.map(|task| Some(task.args.priority)),
|
||||
)
|
||||
.collect::<BTreeSet<_>>();
|
||||
|
||||
let map = priorities
|
||||
.iter()
|
||||
.cloned()
|
||||
.zip(1..)
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
for task in app.hardware_tasks.values_mut() {
|
||||
task.args.priority = map[&Some(task.args.priority)];
|
||||
}
|
||||
|
||||
for task in app.software_tasks.values_mut() {
|
||||
task.args.priority = map[&Some(task.args.priority)];
|
||||
}
|
||||
}
|
||||
}
|
||||
319
rtic-macros/src/syntax/parse.rs
Normal file
319
rtic-macros/src/syntax/parse.rs
Normal file
|
|
@ -0,0 +1,319 @@
|
|||
mod app;
|
||||
mod hardware_task;
|
||||
mod idle;
|
||||
mod init;
|
||||
mod resource;
|
||||
mod software_task;
|
||||
mod util;
|
||||
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use syn::{
|
||||
braced, parenthesized,
|
||||
parse::{self, Parse, ParseStream, Parser},
|
||||
token::Brace,
|
||||
Ident, Item, LitInt, Token,
|
||||
};
|
||||
|
||||
use crate::syntax::{
|
||||
ast::{App, AppArgs, HardwareTaskArgs, IdleArgs, InitArgs, SoftwareTaskArgs, TaskLocal},
|
||||
Either,
|
||||
};
|
||||
|
||||
// Parse the app, both app arguments and body (input)
|
||||
pub fn app(args: TokenStream2, input: TokenStream2) -> parse::Result<App> {
|
||||
let args = AppArgs::parse(args)?;
|
||||
let input: Input = syn::parse2(input)?;
|
||||
|
||||
App::parse(args, input)
|
||||
}
|
||||
|
||||
pub(crate) struct Input {
|
||||
_mod_token: Token![mod],
|
||||
pub ident: Ident,
|
||||
_brace_token: Brace,
|
||||
pub items: Vec<Item>,
|
||||
}
|
||||
|
||||
impl Parse for Input {
|
||||
fn parse(input: ParseStream<'_>) -> parse::Result<Self> {
|
||||
fn parse_items(input: ParseStream<'_>) -> parse::Result<Vec<Item>> {
|
||||
let mut items = vec![];
|
||||
|
||||
while !input.is_empty() {
|
||||
items.push(input.parse()?);
|
||||
}
|
||||
|
||||
Ok(items)
|
||||
}
|
||||
|
||||
let content;
|
||||
|
||||
let _mod_token = input.parse()?;
|
||||
let ident = input.parse()?;
|
||||
let _brace_token = braced!(content in input);
|
||||
let items = content.call(parse_items)?;
|
||||
|
||||
Ok(Input {
|
||||
_mod_token,
|
||||
ident,
|
||||
_brace_token,
|
||||
items,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn init_args(tokens: TokenStream2) -> parse::Result<InitArgs> {
|
||||
(|input: ParseStream<'_>| -> parse::Result<InitArgs> {
|
||||
if input.is_empty() {
|
||||
return Ok(InitArgs::default());
|
||||
}
|
||||
|
||||
let mut local_resources = None;
|
||||
|
||||
let content;
|
||||
parenthesized!(content in input);
|
||||
|
||||
if !content.is_empty() {
|
||||
loop {
|
||||
// Parse identifier name
|
||||
let ident: Ident = content.parse()?;
|
||||
// Handle equal sign
|
||||
let _: Token![=] = content.parse()?;
|
||||
|
||||
match &*ident.to_string() {
|
||||
"local" => {
|
||||
if local_resources.is_some() {
|
||||
return Err(parse::Error::new(
|
||||
ident.span(),
|
||||
"argument appears more than once",
|
||||
));
|
||||
}
|
||||
|
||||
local_resources = Some(util::parse_local_resources(&content)?);
|
||||
}
|
||||
_ => {
|
||||
return Err(parse::Error::new(ident.span(), "unexpected argument"));
|
||||
}
|
||||
}
|
||||
|
||||
if content.is_empty() {
|
||||
break;
|
||||
}
|
||||
// Handle comma: ,
|
||||
let _: Token![,] = content.parse()?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(locals) = &local_resources {
|
||||
for (ident, task_local) in locals {
|
||||
if let TaskLocal::External = task_local {
|
||||
return Err(parse::Error::new(
|
||||
ident.span(),
|
||||
"only declared local resources are allowed in init",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(InitArgs {
|
||||
local_resources: local_resources.unwrap_or_default(),
|
||||
})
|
||||
})
|
||||
.parse2(tokens)
|
||||
}
|
||||
|
||||
fn idle_args(tokens: TokenStream2) -> parse::Result<IdleArgs> {
|
||||
(|input: ParseStream<'_>| -> parse::Result<IdleArgs> {
|
||||
if input.is_empty() {
|
||||
return Ok(IdleArgs::default());
|
||||
}
|
||||
|
||||
let mut shared_resources = None;
|
||||
let mut local_resources = None;
|
||||
|
||||
let content;
|
||||
parenthesized!(content in input);
|
||||
if !content.is_empty() {
|
||||
loop {
|
||||
// Parse identifier name
|
||||
let ident: Ident = content.parse()?;
|
||||
// Handle equal sign
|
||||
let _: Token![=] = content.parse()?;
|
||||
|
||||
match &*ident.to_string() {
|
||||
"shared" => {
|
||||
if shared_resources.is_some() {
|
||||
return Err(parse::Error::new(
|
||||
ident.span(),
|
||||
"argument appears more than once",
|
||||
));
|
||||
}
|
||||
|
||||
shared_resources = Some(util::parse_shared_resources(&content)?);
|
||||
}
|
||||
|
||||
"local" => {
|
||||
if local_resources.is_some() {
|
||||
return Err(parse::Error::new(
|
||||
ident.span(),
|
||||
"argument appears more than once",
|
||||
));
|
||||
}
|
||||
|
||||
local_resources = Some(util::parse_local_resources(&content)?);
|
||||
}
|
||||
|
||||
_ => {
|
||||
return Err(parse::Error::new(ident.span(), "unexpected argument"));
|
||||
}
|
||||
}
|
||||
if content.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
// Handle comma: ,
|
||||
let _: Token![,] = content.parse()?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(IdleArgs {
|
||||
shared_resources: shared_resources.unwrap_or_default(),
|
||||
local_resources: local_resources.unwrap_or_default(),
|
||||
})
|
||||
})
|
||||
.parse2(tokens)
|
||||
}
|
||||
|
||||
fn task_args(tokens: TokenStream2) -> parse::Result<Either<HardwareTaskArgs, SoftwareTaskArgs>> {
|
||||
(|input: ParseStream<'_>| -> parse::Result<Either<HardwareTaskArgs, SoftwareTaskArgs>> {
|
||||
if input.is_empty() {
|
||||
return Ok(Either::Right(SoftwareTaskArgs::default()));
|
||||
}
|
||||
|
||||
let mut binds = None;
|
||||
let mut priority = None;
|
||||
let mut shared_resources = None;
|
||||
let mut local_resources = None;
|
||||
let mut prio_span = None;
|
||||
|
||||
let content;
|
||||
parenthesized!(content in input);
|
||||
loop {
|
||||
if content.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
// Parse identifier name
|
||||
let ident: Ident = content.parse()?;
|
||||
let ident_s = ident.to_string();
|
||||
|
||||
// Handle equal sign
|
||||
let _: Token![=] = content.parse()?;
|
||||
|
||||
match &*ident_s {
|
||||
"binds" => {
|
||||
if binds.is_some() {
|
||||
return Err(parse::Error::new(
|
||||
ident.span(),
|
||||
"argument appears more than once",
|
||||
));
|
||||
}
|
||||
|
||||
// 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() {
|
||||
return Err(parse::Error::new(
|
||||
lit.span(),
|
||||
"this literal must be in the range 0...255",
|
||||
));
|
||||
}
|
||||
|
||||
prio_span = Some(lit.span());
|
||||
priority = Some(value.unwrap());
|
||||
}
|
||||
|
||||
"shared" => {
|
||||
if shared_resources.is_some() {
|
||||
return Err(parse::Error::new(
|
||||
ident.span(),
|
||||
"argument appears more than once",
|
||||
));
|
||||
}
|
||||
|
||||
shared_resources = Some(util::parse_shared_resources(&content)?);
|
||||
}
|
||||
|
||||
"local" => {
|
||||
if local_resources.is_some() {
|
||||
return Err(parse::Error::new(
|
||||
ident.span(),
|
||||
"argument appears more than once",
|
||||
));
|
||||
}
|
||||
|
||||
local_resources = Some(util::parse_local_resources(&content)?);
|
||||
}
|
||||
|
||||
_ => {
|
||||
return Err(parse::Error::new(ident.span(), "unexpected argument"));
|
||||
}
|
||||
}
|
||||
|
||||
if content.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
// Handle comma: ,
|
||||
let _: Token![,] = content.parse()?;
|
||||
}
|
||||
let priority = priority.unwrap_or(1);
|
||||
let shared_resources = shared_resources.unwrap_or_default();
|
||||
let local_resources = local_resources.unwrap_or_default();
|
||||
|
||||
Ok(if let Some(binds) = binds {
|
||||
if priority == 0 {
|
||||
return Err(parse::Error::new(
|
||||
prio_span.unwrap(),
|
||||
"hardware tasks are not allowed to be at priority 0",
|
||||
));
|
||||
}
|
||||
|
||||
Either::Left(HardwareTaskArgs {
|
||||
binds,
|
||||
priority,
|
||||
shared_resources,
|
||||
local_resources,
|
||||
})
|
||||
} else {
|
||||
Either::Right(SoftwareTaskArgs {
|
||||
priority,
|
||||
shared_resources,
|
||||
local_resources,
|
||||
})
|
||||
})
|
||||
})
|
||||
.parse2(tokens)
|
||||
}
|
||||
480
rtic-macros/src/syntax/parse/app.rs
Normal file
480
rtic-macros/src/syntax/parse/app.rs
Normal file
|
|
@ -0,0 +1,480 @@
|
|||
use std::collections::HashSet;
|
||||
|
||||
// use indexmap::map::Entry;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use syn::{
|
||||
parse::{self, ParseStream, Parser},
|
||||
spanned::Spanned,
|
||||
Expr, ExprArray, Fields, ForeignItem, Ident, Item, LitBool, Path, Token, Visibility,
|
||||
};
|
||||
|
||||
use super::Input;
|
||||
use crate::syntax::{
|
||||
ast::{
|
||||
App, AppArgs, Dispatcher, Dispatchers, HardwareTask, Idle, IdleArgs, Init, InitArgs,
|
||||
LocalResource, SharedResource, SoftwareTask,
|
||||
},
|
||||
parse::{self as syntax_parse, util},
|
||||
Either, Map, Set,
|
||||
};
|
||||
|
||||
impl AppArgs {
|
||||
pub(crate) fn parse(tokens: TokenStream2) -> parse::Result<Self> {
|
||||
(|input: ParseStream<'_>| -> parse::Result<Self> {
|
||||
let mut custom = Set::new();
|
||||
let mut device = None;
|
||||
let mut peripherals = true;
|
||||
let mut dispatchers = Dispatchers::new();
|
||||
|
||||
loop {
|
||||
if input.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
// #ident = ..
|
||||
let ident: Ident = input.parse()?;
|
||||
let _eq_token: Token![=] = input.parse()?;
|
||||
|
||||
if custom.contains(&ident) {
|
||||
return Err(parse::Error::new(
|
||||
ident.span(),
|
||||
"argument appears more than once",
|
||||
));
|
||||
}
|
||||
|
||||
custom.insert(ident.clone());
|
||||
|
||||
let ks = ident.to_string();
|
||||
|
||||
match &*ks {
|
||||
"device" => {
|
||||
if let Ok(p) = input.parse::<Path>() {
|
||||
device = Some(p);
|
||||
} else {
|
||||
return Err(parse::Error::new(
|
||||
ident.span(),
|
||||
"unexpected argument value; this should be a path",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
"peripherals" => {
|
||||
if let Ok(p) = input.parse::<LitBool>() {
|
||||
peripherals = p.value;
|
||||
} else {
|
||||
return Err(parse::Error::new(
|
||||
ident.span(),
|
||||
"unexpected argument value; this should be a boolean",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
"dispatchers" => {
|
||||
if let Ok(p) = input.parse::<ExprArray>() {
|
||||
for e in p.elems {
|
||||
match e {
|
||||
Expr::Path(ep) => {
|
||||
let path = ep.path;
|
||||
let ident = if path.leading_colon.is_some()
|
||||
|| path.segments.len() != 1
|
||||
{
|
||||
return Err(parse::Error::new(
|
||||
path.span(),
|
||||
"interrupt must be an identifier, not a path",
|
||||
));
|
||||
} else {
|
||||
path.segments[0].ident.clone()
|
||||
};
|
||||
let span = ident.span();
|
||||
if dispatchers.contains_key(&ident) {
|
||||
return Err(parse::Error::new(
|
||||
span,
|
||||
"this extern interrupt is listed more than once",
|
||||
));
|
||||
} else {
|
||||
dispatchers
|
||||
.insert(ident, Dispatcher { attrs: ep.attrs });
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(parse::Error::new(
|
||||
e.span(),
|
||||
"interrupt must be an identifier",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(parse::Error::new(
|
||||
ident.span(),
|
||||
// increasing the length of the error message will break rustfmt
|
||||
"unexpected argument value; expected an array",
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(parse::Error::new(ident.span(), "unexpected argument"));
|
||||
}
|
||||
}
|
||||
|
||||
if input.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
// ,
|
||||
let _: Token![,] = input.parse()?;
|
||||
}
|
||||
|
||||
let device = if let Some(device) = device {
|
||||
device
|
||||
} else {
|
||||
return Err(parse::Error::new(input.span(), "missing `device = ...`"));
|
||||
};
|
||||
|
||||
Ok(AppArgs {
|
||||
device,
|
||||
peripherals,
|
||||
dispatchers,
|
||||
})
|
||||
})
|
||||
.parse2(tokens)
|
||||
}
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub(crate) fn parse(args: AppArgs, input: Input) -> parse::Result<Self> {
|
||||
let mut init = None;
|
||||
let mut idle = None;
|
||||
|
||||
let mut shared_resources_ident = None;
|
||||
let mut shared_resources = Map::new();
|
||||
let mut local_resources_ident = None;
|
||||
let mut local_resources = Map::new();
|
||||
let mut hardware_tasks = Map::new();
|
||||
let mut software_tasks = Map::new();
|
||||
let mut user_imports = vec![];
|
||||
let mut user_code = vec![];
|
||||
|
||||
let mut seen_idents = HashSet::<Ident>::new();
|
||||
let mut bindings = HashSet::<Ident>::new();
|
||||
|
||||
let mut check_binding = |ident: &Ident| {
|
||||
if bindings.contains(ident) {
|
||||
return Err(parse::Error::new(
|
||||
ident.span(),
|
||||
"this interrupt is already bound",
|
||||
));
|
||||
} else {
|
||||
bindings.insert(ident.clone());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
let mut check_ident = |ident: &Ident| {
|
||||
if seen_idents.contains(ident) {
|
||||
return Err(parse::Error::new(
|
||||
ident.span(),
|
||||
"this identifier has already been used",
|
||||
));
|
||||
} else {
|
||||
seen_idents.insert(ident.clone());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
for mut item in input.items {
|
||||
match item {
|
||||
Item::Fn(mut item) => {
|
||||
let span = item.sig.ident.span();
|
||||
if let Some(pos) = item
|
||||
.attrs
|
||||
.iter()
|
||||
.position(|attr| util::attr_eq(attr, "init"))
|
||||
{
|
||||
let args = InitArgs::parse(item.attrs.remove(pos).tokens)?;
|
||||
|
||||
// If an init function already exists, error
|
||||
if init.is_some() {
|
||||
return Err(parse::Error::new(
|
||||
span,
|
||||
"`#[init]` function must appear at most once",
|
||||
));
|
||||
}
|
||||
|
||||
check_ident(&item.sig.ident)?;
|
||||
|
||||
init = Some(Init::parse(args, item)?);
|
||||
} else if let Some(pos) = item
|
||||
.attrs
|
||||
.iter()
|
||||
.position(|attr| util::attr_eq(attr, "idle"))
|
||||
{
|
||||
let args = IdleArgs::parse(item.attrs.remove(pos).tokens)?;
|
||||
|
||||
// If an idle function already exists, error
|
||||
if idle.is_some() {
|
||||
return Err(parse::Error::new(
|
||||
span,
|
||||
"`#[idle]` function must appear at most once",
|
||||
));
|
||||
}
|
||||
|
||||
check_ident(&item.sig.ident)?;
|
||||
|
||||
idle = Some(Idle::parse(args, item)?);
|
||||
} else if let Some(pos) = item
|
||||
.attrs
|
||||
.iter()
|
||||
.position(|attr| util::attr_eq(attr, "task"))
|
||||
{
|
||||
if hardware_tasks.contains_key(&item.sig.ident)
|
||||
|| software_tasks.contains_key(&item.sig.ident)
|
||||
{
|
||||
return Err(parse::Error::new(
|
||||
span,
|
||||
"this task is defined multiple times",
|
||||
));
|
||||
}
|
||||
|
||||
match syntax_parse::task_args(item.attrs.remove(pos).tokens)? {
|
||||
Either::Left(args) => {
|
||||
check_binding(&args.binds)?;
|
||||
check_ident(&item.sig.ident)?;
|
||||
|
||||
hardware_tasks.insert(
|
||||
item.sig.ident.clone(),
|
||||
HardwareTask::parse(args, item)?,
|
||||
);
|
||||
}
|
||||
|
||||
Either::Right(args) => {
|
||||
check_ident(&item.sig.ident)?;
|
||||
|
||||
software_tasks.insert(
|
||||
item.sig.ident.clone(),
|
||||
SoftwareTask::parse(args, item)?,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Forward normal functions
|
||||
user_code.push(Item::Fn(item.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
Item::Struct(ref mut struct_item) => {
|
||||
// Match structures with the attribute #[shared], name of structure is not
|
||||
// important
|
||||
if let Some(_pos) = struct_item
|
||||
.attrs
|
||||
.iter()
|
||||
.position(|attr| util::attr_eq(attr, "shared"))
|
||||
{
|
||||
let span = struct_item.ident.span();
|
||||
|
||||
shared_resources_ident = Some(struct_item.ident.clone());
|
||||
|
||||
if !shared_resources.is_empty() {
|
||||
return Err(parse::Error::new(
|
||||
span,
|
||||
"`#[shared]` struct must appear at most once",
|
||||
));
|
||||
}
|
||||
|
||||
if struct_item.vis != Visibility::Inherited {
|
||||
return Err(parse::Error::new(
|
||||
struct_item.span(),
|
||||
"this item must have inherited / private visibility",
|
||||
));
|
||||
}
|
||||
|
||||
if let Fields::Named(fields) = &mut struct_item.fields {
|
||||
for field in &mut fields.named {
|
||||
let ident = field.ident.as_ref().expect("UNREACHABLE");
|
||||
|
||||
if shared_resources.contains_key(ident) {
|
||||
return Err(parse::Error::new(
|
||||
ident.span(),
|
||||
"this resource is listed more than once",
|
||||
));
|
||||
}
|
||||
|
||||
shared_resources.insert(
|
||||
ident.clone(),
|
||||
SharedResource::parse(field, ident.span())?,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return Err(parse::Error::new(
|
||||
struct_item.span(),
|
||||
"this `struct` must have named fields",
|
||||
));
|
||||
}
|
||||
} else if let Some(_pos) = struct_item
|
||||
.attrs
|
||||
.iter()
|
||||
.position(|attr| util::attr_eq(attr, "local"))
|
||||
{
|
||||
let span = struct_item.ident.span();
|
||||
|
||||
local_resources_ident = Some(struct_item.ident.clone());
|
||||
|
||||
if !local_resources.is_empty() {
|
||||
return Err(parse::Error::new(
|
||||
span,
|
||||
"`#[local]` struct must appear at most once",
|
||||
));
|
||||
}
|
||||
|
||||
if struct_item.vis != Visibility::Inherited {
|
||||
return Err(parse::Error::new(
|
||||
struct_item.span(),
|
||||
"this item must have inherited / private visibility",
|
||||
));
|
||||
}
|
||||
|
||||
if let Fields::Named(fields) = &mut struct_item.fields {
|
||||
for field in &mut fields.named {
|
||||
let ident = field.ident.as_ref().expect("UNREACHABLE");
|
||||
|
||||
if local_resources.contains_key(ident) {
|
||||
return Err(parse::Error::new(
|
||||
ident.span(),
|
||||
"this resource is listed more than once",
|
||||
));
|
||||
}
|
||||
|
||||
local_resources.insert(
|
||||
ident.clone(),
|
||||
LocalResource::parse(field, ident.span())?,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return Err(parse::Error::new(
|
||||
struct_item.span(),
|
||||
"this `struct` must have named fields",
|
||||
));
|
||||
}
|
||||
} else {
|
||||
// Structure without the #[resources] attribute should just be passed along
|
||||
user_code.push(item.clone());
|
||||
}
|
||||
}
|
||||
|
||||
Item::ForeignMod(mod_) => {
|
||||
if !util::abi_is_rust(&mod_.abi) {
|
||||
return Err(parse::Error::new(
|
||||
mod_.abi.extern_token.span(),
|
||||
"this `extern` block must use the \"Rust\" ABI",
|
||||
));
|
||||
}
|
||||
|
||||
for item in mod_.items {
|
||||
if let ForeignItem::Fn(mut item) = item {
|
||||
let span = item.sig.ident.span();
|
||||
if let Some(pos) = item
|
||||
.attrs
|
||||
.iter()
|
||||
.position(|attr| util::attr_eq(attr, "task"))
|
||||
{
|
||||
if hardware_tasks.contains_key(&item.sig.ident)
|
||||
|| software_tasks.contains_key(&item.sig.ident)
|
||||
{
|
||||
return Err(parse::Error::new(
|
||||
span,
|
||||
"this task is defined multiple times",
|
||||
));
|
||||
}
|
||||
|
||||
if item.attrs.len() != 1 {
|
||||
return Err(parse::Error::new(
|
||||
span,
|
||||
"`extern` task required `#[task(..)]` attribute",
|
||||
));
|
||||
}
|
||||
|
||||
match syntax_parse::task_args(item.attrs.remove(pos).tokens)? {
|
||||
Either::Left(args) => {
|
||||
check_binding(&args.binds)?;
|
||||
check_ident(&item.sig.ident)?;
|
||||
|
||||
hardware_tasks.insert(
|
||||
item.sig.ident.clone(),
|
||||
HardwareTask::parse_foreign(args, item)?,
|
||||
);
|
||||
}
|
||||
|
||||
Either::Right(args) => {
|
||||
check_ident(&item.sig.ident)?;
|
||||
|
||||
software_tasks.insert(
|
||||
item.sig.ident.clone(),
|
||||
SoftwareTask::parse_foreign(args, item)?,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(parse::Error::new(
|
||||
span,
|
||||
"`extern` task required `#[task(..)]` attribute",
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(parse::Error::new(
|
||||
item.span(),
|
||||
"this item must live outside the `#[app]` module",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Item::Use(itemuse_) => {
|
||||
// Store the user provided use-statements
|
||||
user_imports.push(itemuse_.clone());
|
||||
}
|
||||
_ => {
|
||||
// Anything else within the module should not make any difference
|
||||
user_code.push(item.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let shared_resources_ident =
|
||||
shared_resources_ident.expect("No `#[shared]` resource struct defined");
|
||||
let local_resources_ident =
|
||||
local_resources_ident.expect("No `#[local]` resource struct defined");
|
||||
let init = init.expect("No `#[init]` function defined");
|
||||
|
||||
if shared_resources_ident != init.user_shared_struct {
|
||||
return Err(parse::Error::new(
|
||||
init.user_shared_struct.span(),
|
||||
format!(
|
||||
"This name and the one defined on `#[shared]` are not the same. Should this be `{shared_resources_ident}`?"
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
if local_resources_ident != init.user_local_struct {
|
||||
return Err(parse::Error::new(
|
||||
init.user_local_struct.span(),
|
||||
format!(
|
||||
"This name and the one defined on `#[local]` are not the same. Should this be `{local_resources_ident}`?"
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(App {
|
||||
args,
|
||||
name: input.ident,
|
||||
init,
|
||||
idle,
|
||||
shared_resources,
|
||||
local_resources,
|
||||
user_imports,
|
||||
user_code,
|
||||
hardware_tasks,
|
||||
software_tasks,
|
||||
})
|
||||
}
|
||||
}
|
||||
76
rtic-macros/src/syntax/parse/hardware_task.rs
Normal file
76
rtic-macros/src/syntax/parse/hardware_task.rs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
use syn::{parse, ForeignItemFn, ItemFn, Stmt};
|
||||
|
||||
use crate::syntax::parse::util::FilterAttrs;
|
||||
use crate::syntax::{
|
||||
ast::{HardwareTask, HardwareTaskArgs},
|
||||
parse::util,
|
||||
};
|
||||
|
||||
impl HardwareTask {
|
||||
pub(crate) fn parse(args: HardwareTaskArgs, item: ItemFn) -> parse::Result<Self> {
|
||||
let span = item.sig.ident.span();
|
||||
let valid_signature = util::check_fn_signature(&item, false)
|
||||
&& item.sig.inputs.len() == 1
|
||||
&& util::type_is_unit(&item.sig.output);
|
||||
|
||||
let name = item.sig.ident.to_string();
|
||||
|
||||
if valid_signature {
|
||||
if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) {
|
||||
if rest.is_empty() {
|
||||
let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs);
|
||||
|
||||
return Ok(HardwareTask {
|
||||
args,
|
||||
cfgs,
|
||||
attrs,
|
||||
context,
|
||||
stmts: item.block.stmts,
|
||||
is_extern: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(parse::Error::new(
|
||||
span,
|
||||
format!("this task handler must have type signature `fn({name}::Context)`"),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl HardwareTask {
|
||||
pub(crate) fn parse_foreign(
|
||||
args: HardwareTaskArgs,
|
||||
item: ForeignItemFn,
|
||||
) -> parse::Result<Self> {
|
||||
let span = item.sig.ident.span();
|
||||
let valid_signature = util::check_foreign_fn_signature(&item, false)
|
||||
&& item.sig.inputs.len() == 1
|
||||
&& util::type_is_unit(&item.sig.output);
|
||||
|
||||
let name = item.sig.ident.to_string();
|
||||
|
||||
if valid_signature {
|
||||
if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) {
|
||||
if rest.is_empty() {
|
||||
let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs);
|
||||
|
||||
return Ok(HardwareTask {
|
||||
args,
|
||||
cfgs,
|
||||
attrs,
|
||||
context,
|
||||
stmts: Vec::<Stmt>::new(),
|
||||
is_extern: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(parse::Error::new(
|
||||
span,
|
||||
format!("this task handler must have type signature `fn({name}::Context)`"),
|
||||
))
|
||||
}
|
||||
}
|
||||
42
rtic-macros/src/syntax/parse/idle.rs
Normal file
42
rtic-macros/src/syntax/parse/idle.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use syn::{parse, ItemFn};
|
||||
|
||||
use crate::syntax::{
|
||||
ast::{Idle, IdleArgs},
|
||||
parse::util,
|
||||
};
|
||||
|
||||
impl IdleArgs {
|
||||
pub(crate) fn parse(tokens: TokenStream2) -> parse::Result<Self> {
|
||||
crate::syntax::parse::idle_args(tokens)
|
||||
}
|
||||
}
|
||||
|
||||
impl Idle {
|
||||
pub(crate) fn parse(args: IdleArgs, item: ItemFn) -> parse::Result<Self> {
|
||||
let valid_signature = util::check_fn_signature(&item, false)
|
||||
&& item.sig.inputs.len() == 1
|
||||
&& util::type_is_bottom(&item.sig.output);
|
||||
|
||||
let name = item.sig.ident.to_string();
|
||||
|
||||
if valid_signature {
|
||||
if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) {
|
||||
if rest.is_empty() {
|
||||
return Ok(Idle {
|
||||
args,
|
||||
attrs: item.attrs,
|
||||
context,
|
||||
name: item.sig.ident,
|
||||
stmts: item.block.stmts,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(parse::Error::new(
|
||||
item.sig.ident.span(),
|
||||
format!("this `#[idle]` function must have signature `fn({name}::Context) -> !`"),
|
||||
))
|
||||
}
|
||||
}
|
||||
51
rtic-macros/src/syntax/parse/init.rs
Normal file
51
rtic-macros/src/syntax/parse/init.rs
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
use proc_macro2::TokenStream as TokenStream2;
|
||||
|
||||
use syn::{parse, ItemFn};
|
||||
|
||||
use crate::syntax::{
|
||||
ast::{Init, InitArgs},
|
||||
parse::{self as syntax_parse, util},
|
||||
};
|
||||
|
||||
impl InitArgs {
|
||||
pub(crate) fn parse(tokens: TokenStream2) -> parse::Result<Self> {
|
||||
syntax_parse::init_args(tokens)
|
||||
}
|
||||
}
|
||||
|
||||
impl Init {
|
||||
pub(crate) fn parse(args: InitArgs, item: ItemFn) -> parse::Result<Self> {
|
||||
let valid_signature = util::check_fn_signature(&item, false) && item.sig.inputs.len() == 1;
|
||||
|
||||
let span = item.sig.ident.span();
|
||||
|
||||
let name = item.sig.ident.to_string();
|
||||
|
||||
if valid_signature {
|
||||
if let Ok((user_shared_struct, user_local_struct)) =
|
||||
util::type_is_init_return(&item.sig.output)
|
||||
{
|
||||
if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) {
|
||||
if rest.is_empty() {
|
||||
return Ok(Init {
|
||||
args,
|
||||
attrs: item.attrs,
|
||||
context,
|
||||
name: item.sig.ident,
|
||||
stmts: item.block.stmts,
|
||||
user_shared_struct,
|
||||
user_local_struct,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(parse::Error::new(
|
||||
span,
|
||||
format!(
|
||||
"the `#[init]` function must have signature `fn({name}::Context) -> (Shared resources struct, Local resources struct)`"
|
||||
),
|
||||
))
|
||||
}
|
||||
}
|
||||
55
rtic-macros/src/syntax/parse/resource.rs
Normal file
55
rtic-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()),
|
||||
})
|
||||
}
|
||||
}
|
||||
76
rtic-macros/src/syntax/parse/software_task.rs
Normal file
76
rtic-macros/src/syntax/parse/software_task.rs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
use syn::{parse, ForeignItemFn, ItemFn, Stmt};
|
||||
|
||||
use crate::syntax::parse::util::FilterAttrs;
|
||||
use crate::syntax::{
|
||||
ast::{SoftwareTask, SoftwareTaskArgs},
|
||||
parse::util,
|
||||
};
|
||||
|
||||
impl SoftwareTask {
|
||||
pub(crate) fn parse(args: SoftwareTaskArgs, item: ItemFn) -> parse::Result<Self> {
|
||||
let valid_signature = util::check_fn_signature(&item, true)
|
||||
&& util::type_is_unit(&item.sig.output)
|
||||
&& item.sig.asyncness.is_some();
|
||||
|
||||
let span = item.sig.ident.span();
|
||||
|
||||
let name = item.sig.ident.to_string();
|
||||
|
||||
if valid_signature {
|
||||
if let Some((context, Ok(inputs))) = util::parse_inputs(item.sig.inputs, &name) {
|
||||
let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs);
|
||||
|
||||
return Ok(SoftwareTask {
|
||||
args,
|
||||
attrs,
|
||||
cfgs,
|
||||
context,
|
||||
inputs,
|
||||
stmts: item.block.stmts,
|
||||
is_extern: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Err(parse::Error::new(
|
||||
span,
|
||||
format!("this task handler must have type signature `async fn({name}::Context, ..)`"),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl SoftwareTask {
|
||||
pub(crate) fn parse_foreign(
|
||||
args: SoftwareTaskArgs,
|
||||
item: ForeignItemFn,
|
||||
) -> parse::Result<Self> {
|
||||
let valid_signature = util::check_foreign_fn_signature(&item, true)
|
||||
&& util::type_is_unit(&item.sig.output)
|
||||
&& item.sig.asyncness.is_some();
|
||||
|
||||
let span = item.sig.ident.span();
|
||||
|
||||
let name = item.sig.ident.to_string();
|
||||
|
||||
if valid_signature {
|
||||
if let Some((context, Ok(inputs))) = util::parse_inputs(item.sig.inputs, &name) {
|
||||
let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs);
|
||||
|
||||
return Ok(SoftwareTask {
|
||||
args,
|
||||
attrs,
|
||||
cfgs,
|
||||
context,
|
||||
inputs,
|
||||
stmts: Vec::<Stmt>::new(),
|
||||
is_extern: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Err(parse::Error::new(
|
||||
span,
|
||||
format!("this task handler must have type signature `async fn({name}::Context, ..)`"),
|
||||
))
|
||||
}
|
||||
}
|
||||
338
rtic-macros/src/syntax/parse/util.rs
Normal file
338
rtic-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) -> Result<(Ident, Ident), ()> {
|
||||
match ty {
|
||||
ReturnType::Default => Err(()),
|
||||
|
||||
ReturnType::Type(_, ty) => match &**ty {
|
||||
Type::Tuple(t) => {
|
||||
// return should be:
|
||||
// fn -> (User's #[shared] struct, User's #[local] struct)
|
||||
//
|
||||
// We check the length and the last one here, analysis checks that the user
|
||||
// provided structs are correct.
|
||||
if t.elems.len() == 2 {
|
||||
return Ok((
|
||||
extract_init_resource_name_ident(t.elems[0].clone())?,
|
||||
extract_init_resource_name_ident(t.elems[1].clone())?,
|
||||
));
|
||||
}
|
||||
|
||||
Err(())
|
||||
}
|
||||
|
||||
_ => Err(()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn type_is_path(ty: &Type, segments: &[&str]) -> bool {
|
||||
match ty {
|
||||
Type::Path(tpath) if tpath.qself.is_none() => {
|
||||
tpath.path.segments.len() == segments.len()
|
||||
&& tpath
|
||||
.path
|
||||
.segments
|
||||
.iter()
|
||||
.zip(segments)
|
||||
.all(|(lhs, rhs)| lhs.ident == **rhs)
|
||||
}
|
||||
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn type_is_unit(ty: &ReturnType) -> bool {
|
||||
if let ReturnType::Type(_, ty) = ty {
|
||||
if let Type::Tuple(ref tuple) = **ty {
|
||||
tuple.elems.is_empty()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
7
rtic-macros/tests/ui.rs
Normal file
7
rtic-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");
|
||||
}
|
||||
16
rtic-macros/ui/extern-interrupt-used.rs
Normal file
16
rtic-macros/ui/extern-interrupt-used.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#![no_main]
|
||||
|
||||
#[rtic_macros::mock_app(device = mock, dispatchers = [EXTI0])]
|
||||
mod app {
|
||||
#[shared]
|
||||
struct Shared {}
|
||||
|
||||
#[local]
|
||||
struct Local {}
|
||||
|
||||
#[init]
|
||||
fn init(_: init::Context) -> (Shared, Local) {}
|
||||
|
||||
#[task(binds = EXTI0)]
|
||||
fn foo(_: foo::Context) {}
|
||||
}
|
||||
5
rtic-macros/ui/extern-interrupt-used.stderr
Normal file
5
rtic-macros/ui/extern-interrupt-used.stderr
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
error: dispatcher interrupts can't be used as hardware tasks
|
||||
--> ui/extern-interrupt-used.rs:14:20
|
||||
|
|
||||
14 | #[task(binds = EXTI0)]
|
||||
| ^^^^^
|
||||
9
rtic-macros/ui/idle-double-local.rs
Normal file
9
rtic-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
rtic-macros/ui/idle-double-local.stderr
Normal file
5
rtic-macros/ui/idle-double-local.stderr
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
error: argument appears more than once
|
||||
--> ui/idle-double-local.rs:5:25
|
||||
|
|
||||
5 | #[idle(local = [A], local = [B])]
|
||||
| ^^^^^
|
||||
9
rtic-macros/ui/idle-double-shared.rs
Normal file
9
rtic-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
rtic-macros/ui/idle-double-shared.stderr
Normal file
5
rtic-macros/ui/idle-double-shared.stderr
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
error: argument appears more than once
|
||||
--> ui/idle-double-shared.rs:5:26
|
||||
|
|
||||
5 | #[idle(shared = [A], shared = [B])]
|
||||
| ^^^^^^
|
||||
9
rtic-macros/ui/idle-input.rs
Normal file
9
rtic-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
rtic-macros/ui/idle-input.stderr
Normal file
5
rtic-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
rtic-macros/ui/idle-no-context.rs
Normal file
9
rtic-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
rtic-macros/ui/idle-no-context.stderr
Normal file
5
rtic-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
rtic-macros/ui/idle-not-divergent.rs
Normal file
7
rtic-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
rtic-macros/ui/idle-not-divergent.stderr
Normal file
5
rtic-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
rtic-macros/ui/idle-output.rs
Normal file
9
rtic-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
rtic-macros/ui/idle-output.stderr
Normal file
5
rtic-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
rtic-macros/ui/idle-pub.rs
Normal file
9
rtic-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
rtic-macros/ui/idle-pub.stderr
Normal file
5
rtic-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
rtic-macros/ui/idle-unsafe.rs
Normal file
9
rtic-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
rtic-macros/ui/idle-unsafe.stderr
Normal file
5
rtic-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
rtic-macros/ui/init-divergent.rs
Normal file
13
rtic-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
rtic-macros/ui/init-divergent.stderr
Normal file
5
rtic-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)`
|
||||
--> ui/init-divergent.rs:12:8
|
||||
|
|
||||
12 | fn init(_: init::Context) -> ! {}
|
||||
| ^^^^
|
||||
7
rtic-macros/ui/init-double-local.rs
Normal file
7
rtic-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
rtic-macros/ui/init-double-local.stderr
Normal file
5
rtic-macros/ui/init-double-local.stderr
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
error: argument appears more than once
|
||||
--> ui/init-double-local.rs:5:25
|
||||
|
|
||||
5 | #[init(local = [A], local = [B])]
|
||||
| ^^^^^
|
||||
7
rtic-macros/ui/init-double-shared.rs
Normal file
7
rtic-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
rtic-macros/ui/init-double-shared.stderr
Normal file
5
rtic-macros/ui/init-double-shared.stderr
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
error: unexpected argument
|
||||
--> ui/init-double-shared.rs:5:12
|
||||
|
|
||||
5 | #[init(shared = [A], shared = [B])]
|
||||
| ^^^^^^
|
||||
13
rtic-macros/ui/init-input.rs
Normal file
13
rtic-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) {}
|
||||
}
|
||||
5
rtic-macros/ui/init-input.stderr
Normal file
5
rtic-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)`
|
||||
--> ui/init-input.rs:12:8
|
||||
|
|
||||
12 | fn init(_: init::Context, _undef: u32) -> (Shared, Local) {}
|
||||
| ^^^^
|
||||
13
rtic-macros/ui/init-no-context.rs
Normal file
13
rtic-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) {}
|
||||
}
|
||||
5
rtic-macros/ui/init-no-context.stderr
Normal file
5
rtic-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)`
|
||||
--> ui/init-no-context.rs:12:8
|
||||
|
|
||||
12 | fn init() -> (Shared, Local) {}
|
||||
| ^^^^
|
||||
9
rtic-macros/ui/init-output.rs
Normal file
9
rtic-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
rtic-macros/ui/init-output.stderr
Normal file
5
rtic-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)`
|
||||
--> ui/init-output.rs:6:8
|
||||
|
|
||||
6 | fn init(_: init::Context) -> u32 {
|
||||
| ^^^^
|
||||
13
rtic-macros/ui/init-pub.rs
Normal file
13
rtic-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) {}
|
||||
}
|
||||
5
rtic-macros/ui/init-pub.stderr
Normal file
5
rtic-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)`
|
||||
--> ui/init-pub.rs:12:12
|
||||
|
|
||||
12 | pub fn init(_: init::Context) -> (Shared, Local) {}
|
||||
| ^^^^
|
||||
7
rtic-macros/ui/init-unsafe.rs
Normal file
7
rtic-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) {}
|
||||
}
|
||||
5
rtic-macros/ui/init-unsafe.stderr
Normal file
5
rtic-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)`
|
||||
--> ui/init-unsafe.rs:6:15
|
||||
|
|
||||
6 | unsafe fn init(_: init::Context) -> (Shared, Local) {}
|
||||
| ^^^^
|
||||
10
rtic-macros/ui/interrupt-double.rs
Normal file
10
rtic-macros/ui/interrupt-double.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#![no_main]
|
||||
|
||||
#[rtic_macros::mock_app(device = mock)]
|
||||
mod app {
|
||||
#[task(binds = UART0)]
|
||||
fn foo(_: foo::Context) {}
|
||||
|
||||
#[task(binds = UART0)]
|
||||
fn bar(_: bar::Context) {}
|
||||
}
|
||||
5
rtic-macros/ui/interrupt-double.stderr
Normal file
5
rtic-macros/ui/interrupt-double.stderr
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
error: this interrupt is already bound
|
||||
--> ui/interrupt-double.rs:8:20
|
||||
|
|
||||
8 | #[task(binds = UART0)]
|
||||
| ^^^^^
|
||||
18
rtic-macros/ui/local-collision-2.rs
Normal file
18
rtic-macros/ui/local-collision-2.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#![no_main]
|
||||
|
||||
#[rtic_macros::mock_app(device = mock)]
|
||||
mod app {
|
||||
#[shared]
|
||||
struct Shared {}
|
||||
|
||||
#[local]
|
||||
struct Local {
|
||||
a: u32,
|
||||
}
|
||||
|
||||
#[task(local = [a: u8 = 3])]
|
||||
async fn bar(_: bar::Context) {}
|
||||
|
||||
#[init(local = [a: u16 = 2])]
|
||||
fn init(_: init::Context) -> (Shared, Local) {}
|
||||
}
|
||||
17
rtic-macros/ui/local-collision-2.stderr
Normal file
17
rtic-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
|
||||
--> ui/local-collision-2.rs:10:9
|
||||
|
|
||||
10 | a: u32,
|
||||
| ^
|
||||
|
||||
error: Local resource "a" is used by multiple tasks or collides with multiple definitions
|
||||
--> ui/local-collision-2.rs:16:21
|
||||
|
|
||||
16 | #[init(local = [a: u16 = 2])]
|
||||
| ^
|
||||
|
||||
error: Local resource "a" is used by multiple tasks or collides with multiple definitions
|
||||
--> ui/local-collision-2.rs:13:21
|
||||
|
|
||||
13 | #[task(local = [a: u8 = 3])]
|
||||
| ^
|
||||
21
rtic-macros/ui/local-collision.rs
Normal file
21
rtic-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])]
|
||||
async fn foo(_: foo::Context) {}
|
||||
|
||||
#[task(local = [a: u8 = 3])]
|
||||
async fn bar(_: bar::Context) {}
|
||||
|
||||
#[init]
|
||||
fn init(_: init::Context) -> (Shared, Local) {}
|
||||
}
|
||||
11
rtic-macros/ui/local-collision.stderr
Normal file
11
rtic-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
|
||||
--> ui/local-collision.rs:10:9
|
||||
|
|
||||
10 | a: u32,
|
||||
| ^
|
||||
|
||||
error: Local resource "a" is used by multiple tasks or collides with multiple definitions
|
||||
--> ui/local-collision.rs:16:21
|
||||
|
|
||||
16 | #[task(local = [a: u8 = 3])]
|
||||
| ^
|
||||
16
rtic-macros/ui/local-malformed-1.rs
Normal file
16
rtic-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:])]
|
||||
async fn foo(_: foo::Context) {}
|
||||
|
||||
#[init]
|
||||
fn init(_: init::Context) -> (Shared, Local) {}
|
||||
}
|
||||
5
rtic-macros/ui/local-malformed-1.stderr
Normal file
5
rtic-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
rtic-macros/ui/local-malformed-2.rs
Normal file
16
rtic-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])]
|
||||
async fn foo(_: foo::Context) {}
|
||||
|
||||
#[init]
|
||||
fn init(_: init::Context) -> (Shared, Local) {}
|
||||
}
|
||||
5
rtic-macros/ui/local-malformed-2.stderr
Normal file
5
rtic-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
rtic-macros/ui/local-malformed-3.rs
Normal file
16
rtic-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 =])]
|
||||
async fn foo(_: foo::Context) {}
|
||||
|
||||
#[init]
|
||||
fn init(_: init::Context) -> (Shared, Local) {}
|
||||
}
|
||||
5
rtic-macros/ui/local-malformed-3.stderr
Normal file
5
rtic-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
rtic-macros/ui/local-malformed-4.rs
Normal file
16
rtic-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])]
|
||||
async fn foo(_: foo::Context) {}
|
||||
|
||||
#[init]
|
||||
fn init(_: init::Context) -> (Shared, Local) {}
|
||||
}
|
||||
5
rtic-macros/ui/local-malformed-4.stderr
Normal file
5
rtic-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
rtic-macros/ui/local-not-declared.rs
Normal file
16
rtic-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])]
|
||||
async fn foo(_: foo::Context) {}
|
||||
|
||||
#[init]
|
||||
fn init(_: init::Context) -> (Shared, Local) {}
|
||||
}
|
||||
5
rtic-macros/ui/local-not-declared.stderr
Normal file
5
rtic-macros/ui/local-not-declared.stderr
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
error: this local resource has NOT been declared
|
||||
--> ui/local-not-declared.rs:11:21
|
||||
|
|
||||
11 | #[task(local = [A])]
|
||||
| ^
|
||||
15
rtic-macros/ui/local-pub.rs
Normal file
15
rtic-macros/ui/local-pub.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#![no_main]
|
||||
|
||||
#[rtic_macros::mock_app(device = mock)]
|
||||
mod app {
|
||||
#[shared]
|
||||
struct Shared {}
|
||||
|
||||
#[local]
|
||||
struct Local {
|
||||
pub x: u32,
|
||||
}
|
||||
|
||||
#[init]
|
||||
fn init(_: init::Context) -> (Shared, Local) {}
|
||||
}
|
||||
5
rtic-macros/ui/local-pub.stderr
Normal file
5
rtic-macros/ui/local-pub.stderr
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
error: this field must have inherited / private visibility
|
||||
--> ui/local-pub.rs:10:13
|
||||
|
|
||||
10 | pub x: u32,
|
||||
| ^
|
||||
21
rtic-macros/ui/local-shared-attribute.rs
Normal file
21
rtic-macros/ui/local-shared-attribute.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#![no_main]
|
||||
|
||||
#[rtic_macros::mock_app(device = mock)]
|
||||
mod app {
|
||||
#[shared]
|
||||
struct Shared {}
|
||||
|
||||
#[local]
|
||||
struct Local {}
|
||||
|
||||
#[init]
|
||||
fn init(_: init::Context) -> (Shared, Local) {}
|
||||
|
||||
#[task(local = [
|
||||
#[test]
|
||||
a: u32 = 0, // Ok
|
||||
#[test]
|
||||
b, // Error
|
||||
])]
|
||||
fn foo(_: foo::Context) {}
|
||||
}
|
||||
6
rtic-macros/ui/local-shared-attribute.stderr
Normal file
6
rtic-macros/ui/local-shared-attribute.stderr
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
error: attributes are not supported here
|
||||
--> ui/local-shared-attribute.rs:17:9
|
||||
|
|
||||
17 | / #[test]
|
||||
18 | | b, // Error
|
||||
| |_________^
|
||||
28
rtic-macros/ui/local-shared.rs
Normal file
28
rtic-macros/ui/local-shared.rs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#![no_main]
|
||||
|
||||
#[rtic_macros::mock_app(device = mock)]
|
||||
mod app {
|
||||
#[shared]
|
||||
struct Shared {}
|
||||
|
||||
#[local]
|
||||
struct Local {
|
||||
l1: u32,
|
||||
l2: u32,
|
||||
}
|
||||
|
||||
#[init]
|
||||
fn init(_: init::Context) -> (Shared, Local) {}
|
||||
|
||||
// l2 ok
|
||||
#[idle(local = [l2])]
|
||||
fn idle(cx: idle::Context) -> ! {}
|
||||
|
||||
// l1 rejected (not local)
|
||||
#[task(priority = 1, local = [l1])]
|
||||
async fn uart0(cx: uart0::Context) {}
|
||||
|
||||
// l1 rejected (not lock_free)
|
||||
#[task(priority = 2, local = [l1])]
|
||||
async fn uart1(cx: uart1::Context) {}
|
||||
}
|
||||
11
rtic-macros/ui/local-shared.stderr
Normal file
11
rtic-macros/ui/local-shared.stderr
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
error: Local resource "l1" is used by multiple tasks or collides with multiple definitions
|
||||
--> ui/local-shared.rs:22:35
|
||||
|
|
||||
22 | #[task(priority = 1, local = [l1])]
|
||||
| ^^
|
||||
|
||||
error: Local resource "l1" is used by multiple tasks or collides with multiple definitions
|
||||
--> ui/local-shared.rs:26:35
|
||||
|
|
||||
26 | #[task(priority = 2, local = [l1])]
|
||||
| ^^
|
||||
38
rtic-macros/ui/shared-lock-free.rs
Normal file
38
rtic-macros/ui/shared-lock-free.rs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#![no_main]
|
||||
|
||||
#[rtic_macros::mock_app(device = mock)]
|
||||
mod app {
|
||||
#[shared]
|
||||
struct Shared {
|
||||
// An exclusive, early resource
|
||||
#[lock_free]
|
||||
e1: u32,
|
||||
|
||||
// An exclusive, late resource
|
||||
#[lock_free]
|
||||
e2: u32,
|
||||
}
|
||||
|
||||
#[local]
|
||||
struct Local {}
|
||||
|
||||
#[init]
|
||||
fn init(_: init::Context) -> (Shared, Local) {}
|
||||
|
||||
// e2 ok
|
||||
#[idle(shared = [e2])]
|
||||
fn idle(cx: idle::Context) -> ! {
|
||||
debug::exit(debug::EXIT_SUCCESS);
|
||||
loop {}
|
||||
}
|
||||
|
||||
// e1 rejected (not lock_free)
|
||||
#[task(binds = UART0, priority = 1, shared = [e1])]
|
||||
fn uart0(cx: uart0::Context) {
|
||||
*cx.resources.e1 += 10;
|
||||
}
|
||||
|
||||
// e1 rejected (not lock_free)
|
||||
#[task(binds = UART1, priority = 2, shared = [e1])]
|
||||
fn uart1(cx: uart1::Context) {}
|
||||
}
|
||||
17
rtic-macros/ui/shared-lock-free.stderr
Normal file
17
rtic-macros/ui/shared-lock-free.stderr
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
error: Lock free shared resource "e1" is used by tasks at different priorities
|
||||
--> ui/shared-lock-free.rs:9:9
|
||||
|
|
||||
9 | e1: u32,
|
||||
| ^^
|
||||
|
||||
error: Shared resource "e1" is declared lock free but used by tasks at different priorities
|
||||
--> ui/shared-lock-free.rs:30:51
|
||||
|
|
||||
30 | #[task(binds = UART0, priority = 1, shared = [e1])]
|
||||
| ^^
|
||||
|
||||
error: Shared resource "e1" is declared lock free but used by tasks at different priorities
|
||||
--> ui/shared-lock-free.rs:36:51
|
||||
|
|
||||
36 | #[task(binds = UART1, priority = 2, shared = [e1])]
|
||||
| ^^
|
||||
16
rtic-macros/ui/shared-not-declared.rs
Normal file
16
rtic-macros/ui/shared-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(shared = [A])]
|
||||
async fn foo(_: foo::Context) {}
|
||||
|
||||
#[init]
|
||||
fn init(_: init::Context) -> (Shared, Local) {}
|
||||
}
|
||||
5
rtic-macros/ui/shared-not-declared.stderr
Normal file
5
rtic-macros/ui/shared-not-declared.stderr
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
error: this shared resource has NOT been declared
|
||||
--> ui/shared-not-declared.rs:11:22
|
||||
|
|
||||
11 | #[task(shared = [A])]
|
||||
| ^
|
||||
9
rtic-macros/ui/shared-pub.rs
Normal file
9
rtic-macros/ui/shared-pub.rs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#![no_main]
|
||||
|
||||
#[rtic_macros::mock_app(device = mock)]
|
||||
mod app {
|
||||
#[shared]
|
||||
struct Shared {
|
||||
pub x: u32,
|
||||
}
|
||||
}
|
||||
5
rtic-macros/ui/shared-pub.stderr
Normal file
5
rtic-macros/ui/shared-pub.stderr
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
error: this field must have inherited / private visibility
|
||||
--> ui/shared-pub.rs:7:13
|
||||
|
|
||||
7 | pub x: u32,
|
||||
| ^
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue