Move rtic macros to repo root, tune xtask

This commit is contained in:
Henrik Tjäder 2023-02-04 16:47:17 +01:00
parent 4124fbdd61
commit 9e445b3583
134 changed files with 31 additions and 29 deletions

View 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
}

View 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)*)
}

View 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)*
)
}

View 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!()
}
}

View 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
)
}

View 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)*)
}

View 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)
}

View 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
}
)
}

View 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)*
}
)
}
}

View 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
}

View 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
}

View 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,
&quote!(#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
)
}

View 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)
}

View 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)*
)
}

View 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(),
)
}