Break out core specific codegen to bindings

This commit is contained in:
Emil Fresk 2023-02-11 08:55:19 +01:00 committed by Henrik Tjäder
parent 1cda61fbda
commit 60f0342b69
16 changed files with 654 additions and 622 deletions

View file

@ -24,6 +24,7 @@ proc-macro = true
[features]
default = []
debugprint = []
# list of supported codegen backends
thumbv6 = []
thumbv7 = []

View file

@ -1 +0,0 @@

View file

@ -1,70 +1,7 @@
use std::collections::HashSet;
use crate::syntax::ast::App;
use crate::codegen::bindings::architecture_specific_analysis;
use crate::syntax::{analyze::Analysis, 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(())
pub fn app(app: &App, analysis: &Analysis) -> parse::Result<()> {
architecture_specific_analysis(app, analysis)
}

View file

@ -4,6 +4,8 @@ use quote::quote;
use crate::analyze::Analysis;
use crate::syntax::ast::App;
pub mod bindings;
mod assertions;
mod async_dispatchers;
mod hardware_tasks;

View file

@ -1,8 +1,9 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use super::bindings::extra_assertions;
use crate::analyze::Analysis;
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> {
@ -16,38 +17,7 @@ pub fn codegen(app: &App, analysis: &Analysis) -> Vec<TokenStream2> {
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.append(&mut extra_assertions(app, analysis));
stmts
}

View file

@ -0,0 +1,5 @@
// TODO: Feature gate
mod cortex;
// TODO: Feature gate
pub use cortex::*;

View file

@ -0,0 +1,346 @@
use crate::{
analyze::Analysis as CodegenAnalysis,
codegen::util,
syntax::{analyze::Analysis as SyntaxAnalysis, ast::App},
};
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use std::collections::HashSet;
use syn::{parse, Attribute, Ident};
// TODO: This should be feature gated
// pub use basepri::*;
pub use source_masking::*;
/// Whether `name` is an exception with configurable priority
fn is_exception(name: &Ident) -> bool {
let s = name.to_string();
matches!(
&*s,
"MemoryManagement"
| "BusFault"
| "UsageFault"
| "SecureFault"
| "SVCall"
| "DebugMonitor"
| "PendSV"
| "SysTick"
)
}
pub mod source_masking {
use super::*;
use std::collections::HashMap;
/// Generates a `Mutex` implementation
pub fn impl_mutex(
app: &App,
analysis: &CodegenAnalysis,
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)
};
// 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 !is_exception(&task.args.binds) {
Some((&task.args.priority, &task.args.binds))
} else {
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),*])
));
}
// 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!(
#(#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;
const N_CHUNKS: usize = rtic::export::compute_mask_chunks([#(#mask_ids),*]);
const MASKS: [rtic::export::Mask<N_CHUNKS>; 3] = [#(#mask_arr),*];
unsafe {
rtic::export::lock(
#ptr,
CEILING,
&MASKS,
f,
)
}
}
}
)
}
pub fn extra_assertions(_: &App, _: &SyntaxAnalysis) -> Vec<TokenStream2> {
// let device = &app.args.device;
// let no_basepri_checks: Vec<_> = app
// .hardware_tasks
// .iter()
// .filter_map(|(_, task)| {
// if !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: () = {
// #(#no_basepri_checks)*
// };
// let _ = _CONST_CHECK;
// };
// vec![const_check]
vec![]
}
}
pub mod basepri {
use super::*;
/// Generates a `Mutex` implementation
pub fn impl_mutex(
app: &App,
_analysis: &CodegenAnalysis,
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;
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,
f,
)
}
}
}
)
}
pub fn extra_assertions(_: &App, _: &SyntaxAnalysis) -> Vec<TokenStream2> {
vec![]
}
}
pub fn pre_init_checks(app: &App, _: &SyntaxAnalysis) -> Vec<TokenStream2> {
let mut stmts = vec![];
// check that all dispatchers exists in the `Interrupt` enumeration regardless of whether
// they are used or not
let interrupt = util::interrupt_ident();
let rt_err = util::rt_err_ident();
for name in app.args.dispatchers.keys() {
stmts.push(quote!(let _ = #rt_err::#interrupt::#name;));
}
stmts
}
pub fn pre_init_enable_interrupts(app: &App, analysis: &CodegenAnalysis) -> Vec<TokenStream2> {
let mut stmts = vec![];
let interrupt = util::interrupt_ident();
let rt_err = util::rt_err_ident();
let device = &app.args.device;
let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS);
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 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 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
}
pub fn architecture_specific_analysis(app: &App, _: &SyntaxAnalysis) -> 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(())
}

View file

@ -17,7 +17,7 @@ pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 {
match ctxt {
Context::Init => {
fields.push(quote!(
/// Core (Cortex-M) peripherals
/// Core peripherals
pub core: rtic::export::Peripherals
));
@ -25,7 +25,7 @@ pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 {
let device = &app.args.device;
fields.push(quote!(
/// Device peripherals
/// Device peripherals (PAC)
pub device: #device::Peripherals
));

View file

@ -1,15 +1,13 @@
use super::bindings::{pre_init_checks, pre_init_enable_interrupts};
use crate::analyze::Analysis;
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();));
@ -18,68 +16,9 @@ pub fn codegen(app: &App, analysis: &Analysis) -> Vec<TokenStream2> {
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);
stmts.append(&mut pre_init_checks(app, analysis));
// 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.append(&mut pre_init_enable_interrupts(app, analysis));
stmts
}

View file

@ -2,7 +2,8 @@ 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;
use super::bindings::impl_mutex;
/// Generates `static` variables and shared resource proxies
pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 {
@ -75,8 +76,9 @@ pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 {
// For future use
// let doc = format!(" RTIC internal ({} resource): {}:{}", doc, file!(), line!());
mod_app.push(util::impl_mutex(
mod_app.push(impl_mutex(
app,
analysis,
cfgs,
true,
&shared_name,
@ -95,86 +97,6 @@ pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 {
})
};
// 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)*

View file

@ -6,70 +6,11 @@ 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())
@ -204,15 +145,6 @@ 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}"))
}

View file

@ -8,7 +8,6 @@ use proc_macro::TokenStream;
use std::{env, fs, path::Path};
mod analyze;
mod bindings;
mod check;
mod codegen;
mod syntax;
@ -38,7 +37,7 @@ pub fn app(args: TokenStream, input: TokenStream) -> TokenStream {
Ok(x) => x,
};
if let Err(e) = check::app(&app) {
if let Err(e) = check::app(&app, &analysis) {
return e.to_compile_error().into();
}