rtic/macros/src/codegen.rs

2506 lines
68 KiB
Rust
Raw Normal View History

2018-11-03 17:02:41 +01:00
use proc_macro::TokenStream;
use std::collections::{BTreeMap, BTreeSet};
2018-11-03 17:02:41 +01:00
use proc_macro2::Span;
use quote::quote;
use syn::{ArgCaptured, Attribute, Ident, IntSuffix, LitInt};
2018-11-03 17:02:41 +01:00
2018-12-16 19:13:22 +01:00
use crate::{
analyze::{Analysis, Ownership},
syntax::{App, Static},
2018-12-16 19:13:22 +01:00
};
2018-11-03 17:02:41 +01:00
pub fn app(name: &Ident, app: &App, analysis: &Analysis) -> TokenStream {
let (const_app_resources, mod_resources) = resources(app, analysis);
2018-11-03 17:02:41 +01:00
let (
const_app_exceptions,
exception_mods,
exception_locals,
exception_resources,
user_exceptions,
) = exceptions(app, analysis);
let (
const_app_interrupts,
interrupt_mods,
interrupt_locals,
interrupt_resources,
user_interrupts,
) = interrupts(app, analysis);
let (const_app_tasks, task_mods, task_locals, task_resources, user_tasks) =
tasks(app, analysis);
let const_app_dispatchers = dispatchers(&app, analysis);
2018-11-03 17:02:41 +01:00
let const_app_spawn = spawn(app, analysis);
2018-11-03 17:02:41 +01:00
let const_app_tq = timer_queue(app, analysis);
2018-11-03 17:02:41 +01:00
let const_app_schedule = schedule(app);
2018-11-03 17:02:41 +01:00
let assertion_stmts = assertions(app, analysis);
2018-11-03 17:02:41 +01:00
let pre_init_stmts = pre_init(&app, analysis);
2018-11-03 17:02:41 +01:00
let (
const_app_init,
mod_init,
init_locals,
init_resources,
init_late_resources,
user_init,
call_init,
) = init(app, analysis);
2018-11-03 17:02:41 +01:00
let post_init_stmts = post_init(&app, analysis);
let (const_app_idle, mod_idle, idle_locals, idle_resources, user_idle, call_idle) =
idle(app, analysis);
let device = &app.args.device;
quote!(
#user_init
#user_idle
2018-11-03 17:02:41 +01:00
#(#user_exceptions)*
2018-11-03 17:02:41 +01:00
#(#user_interrupts)*
2018-11-03 17:02:41 +01:00
#(#user_tasks)*
2018-11-03 17:02:41 +01:00
#mod_resources
2018-11-03 17:02:41 +01:00
#init_locals
2018-11-03 17:02:41 +01:00
#init_resources
2018-11-03 17:02:41 +01:00
#init_late_resources
2018-11-03 17:02:41 +01:00
#mod_init
2018-11-03 17:02:41 +01:00
#idle_locals
2018-11-03 17:02:41 +01:00
#idle_resources
2018-11-03 17:02:41 +01:00
#mod_idle
2018-11-03 17:02:41 +01:00
#(#exception_locals)*
2018-11-03 17:02:41 +01:00
#(#exception_resources)*
2018-11-03 17:02:41 +01:00
#(#exception_mods)*
2018-11-03 17:02:41 +01:00
#(#interrupt_locals)*
2018-11-03 17:02:41 +01:00
#(#interrupt_resources)*
2018-11-03 17:02:41 +01:00
#(#interrupt_mods)*
2018-11-03 17:02:41 +01:00
#(#task_locals)*
2018-11-03 17:02:41 +01:00
#(#task_resources)*
2018-11-03 17:02:41 +01:00
#(#task_mods)*
2018-11-03 17:02:41 +01:00
/// Implementation details
const #name: () = {
// always include the device crate, which contains the vector table
use #device as _;
2018-11-03 17:02:41 +01:00
#(#const_app_resources)*
2018-11-03 17:02:41 +01:00
#const_app_init
2018-11-03 17:02:41 +01:00
#const_app_idle
2018-11-03 17:02:41 +01:00
#(#const_app_exceptions)*
2018-11-03 17:02:41 +01:00
#(#const_app_interrupts)*
2018-11-03 17:02:41 +01:00
#(#const_app_dispatchers)*
#(#const_app_tasks)*
#(#const_app_spawn)*
#(#const_app_tq)*
#(#const_app_schedule)*
#[no_mangle]
unsafe fn main() -> ! {
#(#assertion_stmts)*
#(#pre_init_stmts)*
#call_init
#(#post_init_stmts)*
#call_idle
}
};
2018-11-03 17:02:41 +01:00
)
.into()
}
/* Main functions */
/// In this pass we generate a static variable and a resource proxy for each resource
///
/// If the user specified a resource like this:
///
/// ```
/// #[rtfm::app(device = ..)]
/// const APP: () = {
/// static mut X: UserDefinedStruct = ();
/// static mut Y: u64 = 0;
/// static mut Z: u32 = 0;
/// }
/// ```
///
/// We'll generate code like this:
///
/// - `const_app`
///
/// ```
/// const APP: () = {
/// static mut X: MaybeUninit<UserDefinedStruct> = MaybeUninit::uninit();
/// static mut Y: u64 = 0;
/// static mut Z: u32 = 0;
///
/// impl<'a> Mutex for resources::X<'a> { .. }
///
/// impl<'a> Mutex for resources::Y<'a> { .. }
///
/// // but not for `Z` because it's not shared and thus requires no proxy
/// };
/// ```
///
/// - `mod_resources`
///
/// ```
/// mod resources {
/// pub struct X<'a> {
/// priority: &'a Priority,
/// }
///
/// impl<'a> X<'a> {
/// pub unsafe fn new(priority: &'a Priority) -> Self {
/// X { priority }
/// }
///
/// pub unsafe fn priority(&self) -> &Priority {
/// self.priority
/// }
/// }
///
/// // same thing for `Y`
///
/// // but not for `Z`
/// }
/// ```
fn resources(
app: &App,
analysis: &Analysis,
) -> (
// const_app
Vec<proc_macro2::TokenStream>,
// mod_resources
proc_macro2::TokenStream,
) {
let mut const_app = vec![];
let mut mod_resources = vec![];
2018-11-03 17:02:41 +01:00
for (name, res) in &app.resources {
let cfgs = &res.cfgs;
2018-11-03 17:02:41 +01:00
let attrs = &res.attrs;
let ty = &res.ty;
if let Some(expr) = res.expr.as_ref() {
const_app.push(quote!(
2018-11-03 17:02:41 +01:00
#(#attrs)*
#(#cfgs)*
static mut #name: #ty = #expr;
));
} else {
const_app.push(quote!(
#(#attrs)*
#(#cfgs)*
static mut #name: core::mem::MaybeUninit<#ty> =
core::mem::MaybeUninit::uninit();
2018-11-03 17:02:41 +01:00
));
}
2018-11-03 17:02:41 +01:00
// generate a resource proxy when needed
if res.mutability.is_some() {
if let Some(Ownership::Shared { ceiling }) = analysis.ownerships.get(name) {
let ptr = if res.expr.is_none() {
quote!(#name.as_mut_ptr())
} else {
quote!(&mut #name)
};
mod_resources.push(quote!(
pub struct #name<'a> {
priority: &'a Priority,
}
2018-11-03 17:02:41 +01:00
impl<'a> #name<'a> {
#[inline(always)]
pub unsafe fn new(priority: &'a Priority) -> Self {
#name { priority }
}
2018-11-03 17:02:41 +01:00
#[inline(always)]
pub unsafe fn priority(&self) -> &Priority {
self.priority
}
}
));
const_app.push(impl_mutex(
app,
cfgs,
true,
name,
quote!(#ty),
*ceiling,
ptr,
));
}
2018-11-03 17:02:41 +01:00
}
}
let mod_resources = if mod_resources.is_empty() {
quote!()
} else {
quote!(mod resources {
use rtfm::export::Priority;
2018-11-03 17:02:41 +01:00
#(#mod_resources)*
})
};
(const_app, mod_resources)
2018-11-03 17:02:41 +01:00
}
// For each exception we'll generate:
//
// - at the root of the crate:
// - a ${name}Resources struct (maybe)
// - a ${name}Locals struct
//
// - a module named after the exception, see the `module` function for more details
//
// - hidden in `const APP`
// - the ${name}Resources constructor
//
// - the exception handler specified by the user
fn exceptions(
app: &App,
analysis: &Analysis,
) -> (
// const_app
Vec<proc_macro2::TokenStream>,
// exception_mods
Vec<proc_macro2::TokenStream>,
// exception_locals
Vec<proc_macro2::TokenStream>,
// exception_resources
Vec<proc_macro2::TokenStream>,
// user_exceptions
Vec<proc_macro2::TokenStream>,
) {
let mut const_app = vec![];
let mut mods = vec![];
let mut locals_structs = vec![];
let mut resources_structs = vec![];
let mut user_code = vec![];
for (name, exception) in &app.exceptions {
let (let_instant, instant) = if cfg!(feature = "timer-queue") {
(
Some(quote!(let instant = rtfm::Instant::now();)),
Some(quote!(, instant)),
)
} else {
(None, None)
};
let priority = &exception.args.priority;
let symbol = exception.args.binds(name);
const_app.push(quote!(
#[allow(non_snake_case)]
#[no_mangle]
unsafe fn #symbol() {
const PRIORITY: u8 = #priority;
#let_instant
rtfm::export::run(PRIORITY, || {
crate::#name(
#name::Locals::new(),
#name::Context::new(&rtfm::export::Priority::new(PRIORITY) #instant)
)
});
2018-11-03 17:02:41 +01:00
}
));
2018-11-03 17:02:41 +01:00
let mut needs_lt = false;
if !exception.args.resources.is_empty() {
let (item, constructor) = resources_struct(
Kind::Exception(name.clone()),
exception.args.priority,
&mut needs_lt,
app,
analysis,
);
2018-11-03 17:02:41 +01:00
resources_structs.push(item);
const_app.push(constructor);
}
mods.push(module(
Kind::Exception(name.clone()),
(!exception.args.resources.is_empty(), needs_lt),
!exception.args.schedule.is_empty(),
!exception.args.spawn.is_empty(),
false,
app,
));
let attrs = &exception.attrs;
let context = &exception.context;
let (locals, lets) = locals(Kind::Exception(name.clone()), &exception.statics);
locals_structs.push(locals);
let use_u32ext = if cfg!(feature = "timer-queue") {
Some(quote!(
use rtfm::U32Ext as _;
))
} else {
None
};
let stmts = &exception.stmts;
user_code.push(quote!(
#(#attrs)*
#[allow(non_snake_case)]
fn #name(__locals: #name::Locals, #context: #name::Context) {
#use_u32ext
use rtfm::Mutex as _;
#(#lets;)*
2018-11-03 17:02:41 +01:00
#(#stmts)*
}
));
}
2018-11-03 17:02:41 +01:00
(
const_app,
mods,
locals_structs,
resources_structs,
user_code,
)
}
2018-11-03 17:02:41 +01:00
// For each interrupt we'll generate:
//
// - at the root of the crate:
// - a ${name}Resources struct (maybe)
// - a ${name}Locals struct
//
// - a module named after the exception, see the `module` function for more details
//
// - hidden in `const APP`
// - the ${name}Resources constructor
//
// - the interrupt handler specified by the user
fn interrupts(
app: &App,
analysis: &Analysis,
) -> (
// const_app
Vec<proc_macro2::TokenStream>,
// interrupt_mods
Vec<proc_macro2::TokenStream>,
// interrupt_locals
Vec<proc_macro2::TokenStream>,
// interrupt_resources
Vec<proc_macro2::TokenStream>,
// user_exceptions
Vec<proc_macro2::TokenStream>,
) {
let mut const_app = vec![];
let mut mods = vec![];
let mut locals_structs = vec![];
let mut resources_structs = vec![];
let mut user_code = vec![];
2018-11-03 17:02:41 +01:00
let device = &app.args.device;
for (name, interrupt) in &app.interrupts {
let (let_instant, instant) = if cfg!(feature = "timer-queue") {
(
Some(quote!(let instant = rtfm::Instant::now();)),
Some(quote!(, instant)),
)
} else {
(None, None)
};
let priority = &interrupt.args.priority;
let symbol = interrupt.args.binds(name);
const_app.push(quote!(
#[allow(non_snake_case)]
#[no_mangle]
unsafe fn #symbol() {
const PRIORITY: u8 = #priority;
2018-11-03 17:02:41 +01:00
#let_instant
2018-11-03 17:02:41 +01:00
// check that this interrupt exists
let _ = #device::Interrupt::#symbol;
rtfm::export::run(PRIORITY, || {
crate::#name(
#name::Locals::new(),
#name::Context::new(&rtfm::export::Priority::new(PRIORITY) #instant)
)
});
}
));
2018-11-03 17:02:41 +01:00
let mut needs_lt = false;
if !interrupt.args.resources.is_empty() {
let (item, constructor) = resources_struct(
Kind::Interrupt(name.clone()),
interrupt.args.priority,
&mut needs_lt,
app,
analysis,
);
2018-11-03 17:02:41 +01:00
resources_structs.push(item);
2018-11-03 17:02:41 +01:00
const_app.push(constructor);
}
2018-11-03 17:02:41 +01:00
mods.push(module(
Kind::Interrupt(name.clone()),
(!interrupt.args.resources.is_empty(), needs_lt),
!interrupt.args.schedule.is_empty(),
!interrupt.args.spawn.is_empty(),
false,
app,
));
2018-11-03 17:02:41 +01:00
let attrs = &interrupt.attrs;
let context = &interrupt.context;
let use_u32ext = if cfg!(feature = "timer-queue") {
Some(quote!(
use rtfm::U32Ext as _;
))
} else {
None
};
let (locals, lets) = locals(Kind::Interrupt(name.clone()), &interrupt.statics);
locals_structs.push(locals);
let stmts = &interrupt.stmts;
user_code.push(quote!(
#(#attrs)*
#[allow(non_snake_case)]
fn #name(__locals: #name::Locals, #context: #name::Context) {
#use_u32ext
use rtfm::Mutex as _;
2019-02-16 00:22:00 +01:00
#(#lets;)*
#(#stmts)*
}
));
}
(
const_app,
mods,
locals_structs,
resources_structs,
user_code,
2018-11-03 17:02:41 +01:00
)
}
// For each task we'll generate:
//
// - at the root of the crate:
// - a ${name}Resources struct (maybe)
// - a ${name}Locals struct
//
// - a module named after the task, see the `module` function for more details
//
// - hidden in `const APP`
// - the ${name}Resources constructor
// - an INPUTS buffer
// - a free queue and a corresponding resource
// - an INSTANTS buffer (if `timer-queue` is enabled)
//
// - the task handler specified by the user
fn tasks(
app: &App,
analysis: &Analysis,
) -> (
// const_app
Vec<proc_macro2::TokenStream>,
// task_mods
Vec<proc_macro2::TokenStream>,
// task_locals
Vec<proc_macro2::TokenStream>,
// task_resources
Vec<proc_macro2::TokenStream>,
// user_tasks
Vec<proc_macro2::TokenStream>,
) {
let mut const_app = vec![];
let mut mods = vec![];
let mut locals_structs = vec![];
let mut resources_structs = vec![];
let mut user_code = vec![];
2018-11-03 17:02:41 +01:00
for (name, task) in &app.tasks {
let inputs = &task.inputs;
let (_, _, _, ty) = regroup_inputs(inputs);
2019-04-21 18:20:57 +02:00
let cap = analysis.capacities[name];
let cap_lit = mk_capacity_literal(cap);
let cap_ty = mk_typenum_capacity(cap, true);
2019-04-21 18:20:57 +02:00
let task_inputs = mk_inputs_ident(name);
let task_instants = mk_instants_ident(name);
let task_fq = mk_fq_ident(name);
2018-11-03 17:02:41 +01:00
let elems = (0..cap)
.map(|_| quote!(core::mem::MaybeUninit::uninit()))
.collect::<Vec<_>>();
2019-04-21 18:20:57 +02:00
if cfg!(feature = "timer-queue") {
let elems = elems.clone();
const_app.push(quote!(
/// Buffer that holds the instants associated to the inputs of a task
static mut #task_instants: [core::mem::MaybeUninit<rtfm::Instant>; #cap_lit] =
[#(#elems,)*];
));
}
2019-04-21 18:20:57 +02:00
const_app.push(quote!(
/// Buffer that holds the inputs of a task
static mut #task_inputs: [core::mem::MaybeUninit<#ty>; #cap_lit] =
[#(#elems,)*];
));
2018-11-03 17:02:41 +01:00
let doc = "Queue version of a free-list that keeps track of empty slots in the previous buffer(s)";
let fq_ty = quote!(rtfm::export::FreeQueue<#cap_ty>);
let ptr = if cfg!(feature = "nightly") {
const_app.push(quote!(
#[doc = #doc]
static mut #task_fq: #fq_ty = unsafe {
rtfm::export::FreeQueue::u8_sc()
};
));
2018-11-03 17:02:41 +01:00
quote!(&mut #task_fq)
} else {
const_app.push(quote!(
#[doc = #doc]
static mut #task_fq: core::mem::MaybeUninit<#fq_ty> =
core::mem::MaybeUninit::uninit();
));
2018-11-03 17:02:41 +01:00
quote!(#task_fq.as_mut_ptr())
};
if let Some(ceiling) = analysis.free_queues.get(name) {
const_app.push(quote!(struct #task_fq<'a> {
priority: &'a rtfm::export::Priority,
}));
const_app.push(impl_mutex(app, &[], false, &task_fq, fq_ty, *ceiling, ptr));
}
let mut needs_lt = false;
if !task.args.resources.is_empty() {
let (item, constructor) = resources_struct(
Kind::Task(name.clone()),
task.args.priority,
&mut needs_lt,
app,
analysis,
);
resources_structs.push(item);
const_app.push(constructor);
}
mods.push(module(
Kind::Task(name.clone()),
(!task.args.resources.is_empty(), needs_lt),
!task.args.schedule.is_empty(),
!task.args.spawn.is_empty(),
false,
app,
));
let attrs = &task.attrs;
let use_u32ext = if cfg!(feature = "timer-queue") {
Some(quote!(
use rtfm::U32Ext as _;
))
} else {
None
};
let context = &task.context;
let stmts = &task.stmts;
let (locals_struct, lets) = locals(Kind::Task(name.clone()), &task.statics);
locals_structs.push(locals_struct);
user_code.push(quote!(
#(#attrs)*
#[allow(non_snake_case)]
fn #name(__locals: #name::Locals, #context: #name::Context #(,#inputs)*) {
use rtfm::Mutex as _;
#use_u32ext
#(#lets;)*
#(#stmts)*
}
));
2018-11-03 17:02:41 +01:00
}
(
const_app,
mods,
locals_structs,
resources_structs,
user_code,
)
2018-11-03 17:02:41 +01:00
}
/// For each task dispatcher we'll generate
///
/// - A static variable that hold the ready queue (`RQ${priority}`) and a resource proxy for it
/// - An enumeration of all the tasks dispatched by this dispatcher `T${priority}`
/// - An interrupt handler that dispatches the tasks
fn dispatchers(app: &App, analysis: &Analysis) -> Vec<proc_macro2::TokenStream> {
2018-11-03 17:02:41 +01:00
let mut items = vec![];
let device = &app.args.device;
for (level, dispatcher) in &analysis.dispatchers {
let rq = mk_rq_ident(*level);
let t = mk_t_ident(*level);
let cap = mk_typenum_capacity(dispatcher.capacity, true);
2018-11-03 17:02:41 +01:00
let doc = format!(
"Queue of tasks ready to be dispatched at priority level {}",
level
);
let rq_ty = quote!(rtfm::export::ReadyQueue<#t, #cap>);
let ptr = if cfg!(feature = "nightly") {
items.push(quote!(
#[doc = #doc]
static mut #rq: #rq_ty = unsafe {
rtfm::export::ReadyQueue::u8_sc()
};
));
2018-11-03 17:02:41 +01:00
quote!(&mut #rq)
} else {
items.push(quote!(
#[doc = #doc]
static mut #rq: core::mem::MaybeUninit<#rq_ty> =
core::mem::MaybeUninit::uninit();
2018-11-03 17:02:41 +01:00
));
quote!(#rq.as_mut_ptr())
};
if let Some(ceiling) = analysis.ready_queues.get(&level) {
items.push(quote!(
struct #rq<'a> {
priority: &'a rtfm::export::Priority,
}
));
items.push(impl_mutex(app, &[], false, &rq, rq_ty, *ceiling, ptr));
2018-11-03 17:02:41 +01:00
}
let variants = dispatcher
.tasks
.iter()
.map(|task| {
let cfgs = &app.tasks[task].cfgs;
quote!(
#(#cfgs)*
#task
)
})
.collect::<Vec<_>>();
let doc = format!(
"Software tasks to be dispatched at priority level {}",
level
);
items.push(quote!(
#[allow(non_camel_case_types)]
#[derive(Clone, Copy)]
#[doc = #doc]
enum #t {
#(#variants,)*
2018-11-03 17:02:41 +01:00
}
));
let arms = dispatcher
.tasks
.iter()
.map(|name| {
let task = &app.tasks[name];
let cfgs = &task.cfgs;
let (_, tupled, pats, _) = regroup_inputs(&task.inputs);
let inputs = mk_inputs_ident(name);
let fq = mk_fq_ident(name);
let input = quote!(#inputs.get_unchecked(usize::from(index)).as_ptr().read());
let fq = if cfg!(feature = "nightly") {
quote!(#fq)
} else {
quote!((*#fq.as_mut_ptr()))
};
let (let_instant, _instant) = if cfg!(feature = "timer-queue") {
let instants = mk_instants_ident(name);
let instant =
quote!(#instants.get_unchecked(usize::from(index)).as_ptr().read());
(
Some(quote!(let instant = #instant;)),
Some(quote!(, instant)),
)
} else {
(None, None)
};
let call = {
let pats = pats.clone();
quote!(
#name(
#name::Locals::new(),
#name::Context::new(priority #_instant)
#(,#pats)*
)
)
};
quote!(
#(#cfgs)*
#t::#name => {
let #tupled = #input;
#let_instant
#fq.split().0.enqueue_unchecked(index);
let priority = &rtfm::export::Priority::new(PRIORITY);
#call
}
)
})
.collect::<Vec<_>>();
let doc = format!(
"interrupt handler used to dispatch tasks at priority {}",
level
);
let attrs = &dispatcher.attrs;
let interrupt = &dispatcher.interrupt;
let rq = if cfg!(feature = "nightly") {
quote!((&mut #rq))
} else {
quote!((*#rq.as_mut_ptr()))
};
items.push(quote!(
#[doc = #doc]
#(#attrs)*
#[no_mangle]
#[allow(non_snake_case)]
unsafe fn #interrupt() {
/// The priority of this interrupt handler
const PRIORITY: u8 = #level;
// check that this interrupt exists
let _ = #device::Interrupt::#interrupt;
rtfm::export::run(PRIORITY, || {
while let Some((task, index)) = #rq.split().1.dequeue() {
match task {
#(#arms)*
}
}
});
}
));
}
items
}
/// Generates all the `Spawn.$task` related code
fn spawn(app: &App, analysis: &Analysis) -> Vec<proc_macro2::TokenStream> {
let mut items = vec![];
let mut seen = BTreeSet::new();
for (spawner, spawnees) in app.spawn_callers() {
if spawnees.is_empty() {
continue;
2018-11-03 17:02:41 +01:00
}
let mut methods = vec![];
let spawner_is_init = spawner == "init";
let spawner_is_idle = spawner == "idle";
for name in spawnees {
let spawnee = &app.tasks[name];
let cfgs = &spawnee.cfgs;
let (args, _, untupled, ty) = regroup_inputs(&spawnee.inputs);
if spawner_is_init {
// `init` uses a special spawn implementation; it doesn't use the `spawn_${name}`
// functions which are shared by other contexts
let body = mk_spawn_body(&spawner, &name, app, analysis);
let let_instant = if cfg!(feature = "timer-queue") {
Some(quote!(let instant = unsafe { rtfm::Instant::artificial(0) };))
} else {
None
};
methods.push(quote!(
#(#cfgs)*
fn #name(&self #(,#args)*) -> Result<(), #ty> {
#let_instant
#body
}
));
} else {
let spawn = mk_spawn_ident(name);
if !seen.contains(name) {
// generate a `spawn_${name}` function
seen.insert(name);
let instant = if cfg!(feature = "timer-queue") {
Some(quote!(, instant: rtfm::Instant))
} else {
None
};
let body = mk_spawn_body(&spawner, &name, app, analysis);
let args = args.clone();
items.push(quote!(
#(#cfgs)*
unsafe fn #spawn(
priority: &rtfm::export::Priority
#instant
#(,#args)*
) -> Result<(), #ty> {
#body
}
));
}
let (let_instant, instant) = if cfg!(feature = "timer-queue") {
(
Some(if spawner_is_idle {
quote!(let instant = rtfm::Instant::now();)
} else {
quote!(let instant = self.instant();)
}),
Some(quote!(, instant)),
)
} else {
(None, None)
};
methods.push(quote!(
#(#cfgs)*
#[inline(always)]
fn #name(&self #(,#args)*) -> Result<(), #ty> {
unsafe {
#let_instant
#spawn(self.priority() #instant #(,#untupled)*)
}
}
2018-11-03 17:02:41 +01:00
));
}
}
let lt = if spawner_is_init {
None
} else {
Some(quote!('a))
};
items.push(quote!(
impl<#lt> #spawner::Spawn<#lt> {
#(#methods)*
}
));
2018-11-03 17:02:41 +01:00
}
items
}
2018-11-03 17:02:41 +01:00
/// Generates code related to the timer queue, namely
///
/// - A static variable that holds the timer queue and a resource proxy for it
/// - The system timer exception, which moves tasks from the timer queue into the ready queues
fn timer_queue(app: &App, analysis: &Analysis) -> Vec<proc_macro2::TokenStream> {
let mut items = vec![];
let tasks = &analysis.timer_queue.tasks;
if tasks.is_empty() {
return items;
}
let variants = tasks
.iter()
.map(|task| {
let cfgs = &app.tasks[task].cfgs;
quote!(
#(#cfgs)*
#task
)
})
.collect::<Vec<_>>();
items.push(quote!(
/// `schedule`-dable tasks
#[allow(non_camel_case_types)]
#[derive(Clone, Copy)]
enum T {
#(#variants,)*
}
));
let cap = mk_typenum_capacity(analysis.timer_queue.capacity, false);
let ty = quote!(rtfm::export::TimerQueue<T, #cap>);
items.push(quote!(
/// The timer queue
static mut TQ: core::mem::MaybeUninit<#ty> = core::mem::MaybeUninit::uninit();
));
items.push(quote!(
struct TQ<'a> {
priority: &'a rtfm::export::Priority,
}
));
items.push(impl_mutex(
app,
&[],
false,
&Ident::new("TQ", Span::call_site()),
ty,
analysis.timer_queue.ceiling,
quote!(TQ.as_mut_ptr()),
));
let device = &app.args.device;
let arms = tasks
.iter()
.map(|name| {
let task = &app.tasks[name];
let cfgs = &task.cfgs;
let priority = task.args.priority;
let rq = mk_rq_ident(priority);
let t = mk_t_ident(priority);
let dispatcher = &analysis.dispatchers[&priority].interrupt;
quote!(
#(#cfgs)*
T::#name => {
let priority = &rtfm::export::Priority::new(PRIORITY);
(#rq { priority }).lock(|rq| {
rq.split().0.enqueue_unchecked((#t::#name, index))
});
rtfm::pend(#device::Interrupt::#dispatcher)
}
)
})
.collect::<Vec<_>>();
let priority = analysis.timer_queue.priority;
items.push(quote!(
/// The system timer
#[no_mangle]
unsafe fn SysTick() {
use rtfm::Mutex as _;
/// System timer priority
const PRIORITY: u8 = #priority;
rtfm::export::run(PRIORITY, || {
while let Some((task, index)) = (TQ {
// NOTE dynamic priority is always the static priority at this point
priority: &rtfm::export::Priority::new(PRIORITY),
})
// NOTE `inline(always)` produces faster and smaller code
.lock(#[inline(always)]
|tq| tq.dequeue())
{
match task {
#(#arms)*
}
}
});
}
));
items
}
/// Generates all the `Schedule.$task` related code
fn schedule(app: &App) -> Vec<proc_macro2::TokenStream> {
let mut items = vec![];
if !cfg!(feature = "timer-queue") {
return items;
}
2018-11-03 17:02:41 +01:00
let mut seen = BTreeSet::new();
for (scheduler, schedulees) in app.schedule_callers() {
if schedulees.is_empty() {
continue;
}
let mut methods = vec![];
let scheduler_is_init = scheduler == "init";
for name in schedulees {
let schedulee = &app.tasks[name];
let (args, _, untupled, ty) = regroup_inputs(&schedulee.inputs);
let cfgs = &schedulee.cfgs;
let schedule = mk_schedule_ident(name);
if scheduler_is_init {
let body = mk_schedule_body(&scheduler, name, app);
let args = args.clone();
methods.push(quote!(
#(#cfgs)*
fn #name(&self, instant: rtfm::Instant #(,#args)*) -> Result<(), #ty> {
#body
}
));
} else {
if !seen.contains(name) {
seen.insert(name);
let body = mk_schedule_body(&scheduler, name, app);
let args = args.clone();
items.push(quote!(
#(#cfgs)*
fn #schedule(
priority: &rtfm::export::Priority,
instant: rtfm::Instant
#(,#args)*
) -> Result<(), #ty> {
#body
}
));
}
methods.push(quote!(
#(#cfgs)*
#[inline(always)]
fn #name(&self, instant: rtfm::Instant #(,#args)*) -> Result<(), #ty> {
let priority = unsafe { self.priority() };
#schedule(priority, instant #(,#untupled)*)
}
));
}
}
let lt = if scheduler_is_init {
None
} else {
Some(quote!('a))
};
2018-11-03 17:02:41 +01:00
items.push(quote!(
impl<#lt> #scheduler::Schedule<#lt> {
#(#methods)*
2018-11-03 17:02:41 +01:00
}
));
}
items
}
2018-11-03 17:02:41 +01:00
/// Generates `Send` / `Sync` compile time checks
fn assertions(app: &App, analysis: &Analysis) -> Vec<proc_macro2::TokenStream> {
let mut stmts = vec![];
2018-11-03 17:02:41 +01:00
for ty in &analysis.assert_sync {
stmts.push(quote!(rtfm::export::assert_sync::<#ty>();));
}
for task in &analysis.tasks_assert_send {
let (_, _, _, ty) = regroup_inputs(&app.tasks[task].inputs);
stmts.push(quote!(rtfm::export::assert_send::<#ty>();));
}
// all late resources need to be `Send`
for ty in &analysis.resources_assert_send {
stmts.push(quote!(rtfm::export::assert_send::<#ty>();));
}
stmts
}
/// Generates code that we must run before `init` runs. See comments inside
fn pre_init(app: &App, analysis: &Analysis) -> Vec<proc_macro2::TokenStream> {
let mut stmts = vec![];
stmts.push(quote!(rtfm::export::interrupt::disable();));
// these won't be required once we have better `const fn` on stable (or const generics)
if !cfg!(feature = "nightly") {
// initialize `MaybeUninit` `ReadyQueue`s
for level in analysis.dispatchers.keys() {
let rq = mk_rq_ident(*level);
stmts.push(quote!(#rq.as_mut_ptr().write(rtfm::export::ReadyQueue::u8_sc());))
}
// initialize `MaybeUninit` `FreeQueue`s
for name in app.tasks.keys() {
let fq = mk_fq_ident(name);
stmts.push(quote!(
let fq = #fq.as_mut_ptr().write(rtfm::export::FreeQueue::u8_sc());
2018-11-03 17:02:41 +01:00
));
// populate the `FreeQueue`s
let cap = analysis.capacities[name];
stmts.push(quote!(
for i in 0..#cap {
fq.enqueue_unchecked(i);
}
));
}
} else {
// populate the `FreeQueue`s
for name in app.tasks.keys() {
let fq = mk_fq_ident(name);
let cap = analysis.capacities[name];
stmts.push(quote!(
for i in 0..#cap {
#fq.enqueue_unchecked(i);
2018-11-03 17:02:41 +01:00
}
));
}
}
stmts.push(quote!(
let mut core = rtfm::export::Peripherals::steal();
));
2018-11-03 17:02:41 +01:00
// Initialize the timer queue
if !analysis.timer_queue.tasks.is_empty() {
stmts.push(quote!(TQ.as_mut_ptr().write(rtfm::export::TimerQueue::new(core.SYST));));
}
2018-11-03 17:02:41 +01:00
// set interrupts priorities
let device = &app.args.device;
let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS);
for (handler, interrupt) in &app.interrupts {
let name = interrupt.args.binds(handler);
let priority = interrupt.args.priority;
2018-11-03 17:02:41 +01:00
stmts.push(quote!(core.NVIC.enable(#device::Interrupt::#name);));
2018-11-03 17:02:41 +01:00
// compile time assert that this priority is supported by the device
stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];));
2018-11-03 17:02:41 +01:00
stmts.push(quote!(
core.NVIC.set_priority(
#device::Interrupt::#name,
rtfm::export::logical2hw(#priority, #nvic_prio_bits),
);
));
}
// set task dispatcher priorities
for (priority, dispatcher) in &analysis.dispatchers {
let name = &dispatcher.interrupt;
2018-11-03 17:02:41 +01:00
stmts.push(quote!(core.NVIC.enable(#device::Interrupt::#name);));
2018-11-03 17:02:41 +01:00
// compile time assert that this priority is supported by the device
stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];));
stmts.push(quote!(
core.NVIC.set_priority(
#device::Interrupt::#name,
rtfm::export::logical2hw(#priority, #nvic_prio_bits),
);
));
}
// Set the cycle count to 0 and disable it while `init` executes
if cfg!(feature = "timer-queue") {
stmts.push(quote!(core.DWT.ctrl.modify(|r| r & !1);));
stmts.push(quote!(core.DWT.cyccnt.write(0);));
}
stmts
2018-11-03 17:02:41 +01:00
}
// This generates
//
// - at the root of the crate
// - a initResources struct (maybe)
// - a initLateResources struct (maybe)
// - a initLocals struct
//
// - an `init` module that contains
// - the `Context` struct
// - a re-export of the initResources struct
// - a re-export of the initLateResources struct
// - a re-export of the initLocals struct
// - the Spawn struct (maybe)
// - the Schedule struct (maybe, if `timer-queue` is enabled)
//
// - hidden in `const APP`
// - the initResources constructor
//
// - the user specified `init` function
//
// - a call to the user specified `init` function
fn init(
2018-11-03 17:02:41 +01:00
app: &App,
analysis: &Analysis,
) -> (
// const_app
Option<proc_macro2::TokenStream>,
// mod_init
proc_macro2::TokenStream,
// init_locals
proc_macro2::TokenStream,
// init_resources
Option<proc_macro2::TokenStream>,
// init_late_resources
Option<proc_macro2::TokenStream>,
// user_init
proc_macro2::TokenStream,
// call_init
proc_macro2::TokenStream,
) {
let mut needs_lt = false;
let mut const_app = None;
let mut init_resources = None;
if !app.init.args.resources.is_empty() {
let (item, constructor) = resources_struct(Kind::Init, 0, &mut needs_lt, app, analysis);
init_resources = Some(item);
const_app = Some(constructor);
}
2018-11-03 17:02:41 +01:00
let core = if cfg!(feature = "timer-queue") {
quote!(rtfm::Peripherals {
CBP: core.CBP,
CPUID: core.CPUID,
DCB: &mut core.DCB,
FPB: core.FPB,
FPU: core.FPU,
ITM: core.ITM,
MPU: core.MPU,
SCB: &mut core.SCB,
TPIU: core.TPIU,
})
2018-11-03 17:02:41 +01:00
} else {
quote!(rtfm::Peripherals {
CBP: core.CBP,
CPUID: core.CPUID,
DCB: core.DCB,
DWT: core.DWT,
FPB: core.FPB,
FPU: core.FPU,
ITM: core.ITM,
MPU: core.MPU,
SCB: &mut core.SCB,
SYST: core.SYST,
TPIU: core.TPIU,
})
2018-11-03 17:02:41 +01:00
};
let call_init = quote!(let late = init(init::Locals::new(), init::Context::new(#core)););
2018-11-03 17:02:41 +01:00
let late_fields = app
.resources
.iter()
.filter_map(|(name, res)| {
if res.expr.is_none() {
let ty = &res.ty;
2018-11-03 17:02:41 +01:00
Some(quote!(pub #name: #ty))
} else {
None
}
})
.collect::<Vec<_>>();
let attrs = &app.init.attrs;
let has_late_resources = !late_fields.is_empty();
let (ret, init_late_resources) = if has_late_resources {
(
Some(quote!(-> init::LateResources)),
Some(quote!(
/// Resources initialized at runtime
#[allow(non_snake_case)]
pub struct initLateResources {
#(#late_fields),*
2018-11-03 17:02:41 +01:00
}
)),
)
} else {
(None, None)
};
let context = &app.init.context;
let use_u32ext = if cfg!(feature = "timer-queue") {
Some(quote!(
use rtfm::U32Ext as _;
))
} else {
None
};
let (locals_struct, lets) = locals(Kind::Init, &app.init.statics);
let stmts = &app.init.stmts;
let user_init = quote!(
#(#attrs)*
#[allow(non_snake_case)]
fn init(__locals: init::Locals, #context: init::Context) #ret {
#use_u32ext
#(#lets;)*
#(#stmts)*
2018-11-03 17:02:41 +01:00
}
);
2018-11-03 17:02:41 +01:00
let mod_init = module(
Kind::Init,
(!app.init.args.resources.is_empty(), needs_lt),
!app.init.args.schedule.is_empty(),
!app.init.args.spawn.is_empty(),
has_late_resources,
app,
);
2018-11-03 17:02:41 +01:00
(
const_app,
mod_init,
locals_struct,
init_resources,
init_late_resources,
user_init,
call_init,
)
}
2018-11-03 17:02:41 +01:00
/// Generates code that we must run after `init` returns. See comments inside
fn post_init(app: &App, analysis: &Analysis) -> Vec<proc_macro2::TokenStream> {
let mut stmts = vec![];
2018-11-03 17:02:41 +01:00
let device = &app.args.device;
let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS);
// initialize late resources
for (name, res) in &app.resources {
if res.expr.is_some() {
continue;
2018-11-03 17:02:41 +01:00
}
stmts.push(quote!(#name.as_mut_ptr().write(late.#name);));
2018-11-03 17:02:41 +01:00
}
// set exception priorities
for (handler, exception) in &app.exceptions {
let name = exception.args.binds(handler);
let priority = exception.args.priority;
// compile time assert that this priority is supported by the device
stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];));
stmts.push(quote!(core.SCB.set_priority(
rtfm::export::SystemHandler::#name,
rtfm::export::logical2hw(#priority, #nvic_prio_bits),
);));
2018-11-03 17:02:41 +01:00
}
// set the system timer priority
if !analysis.timer_queue.tasks.is_empty() {
let priority = analysis.timer_queue.priority;
2018-11-03 17:02:41 +01:00
// compile time assert that this priority is supported by the device
stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];));
2018-11-03 17:02:41 +01:00
stmts.push(quote!(core.SCB.set_priority(
rtfm::export::SystemHandler::SysTick,
rtfm::export::logical2hw(#priority, #nvic_prio_bits),
);));
}
2018-11-03 17:02:41 +01:00
if app.idle.is_none() {
// Set SLEEPONEXIT bit to enter sleep mode when returning from ISR
stmts.push(quote!(core.SCB.scr.modify(|r| r | 1 << 1);));
2018-11-03 17:02:41 +01:00
}
// enable and start the system timer
if !analysis.timer_queue.tasks.is_empty() {
stmts.push(quote!((*TQ.as_mut_ptr())
.syst
.set_clock_source(rtfm::export::SystClkSource::Core);));
stmts.push(quote!((*TQ.as_mut_ptr()).syst.enable_counter();));
}
2018-11-03 17:02:41 +01:00
// enable the cycle counter
if cfg!(feature = "timer-queue") {
stmts.push(quote!(core.DCB.enable_trace();));
stmts.push(quote!(core.DWT.enable_cycle_counter();));
2018-11-03 17:02:41 +01:00
}
stmts.push(quote!(rtfm::export::interrupt::enable();));
stmts
2018-11-03 17:02:41 +01:00
}
// If the user specified `idle` this generates
//
// - at the root of the crate
// - an idleResources struct (maybe)
// - an idleLocals struct
//
// - an `init` module that contains
// - the `Context` struct
// - a re-export of the idleResources struct
// - a re-export of the idleLocals struct
// - the Spawn struct (maybe)
// - the Schedule struct (maybe, if `timer-queue` is enabled)
//
// - hidden in `const APP`
// - the idleResources constructor
//
// - the user specified `idle` function
//
// - a call to the user specified `idle` function
//
// Otherwise it uses `loop { WFI }` as `idle`
2018-11-03 17:02:41 +01:00
fn idle(
app: &App,
analysis: &Analysis,
) -> (
// const_app_idle
Option<proc_macro2::TokenStream>,
// mod_idle
Option<proc_macro2::TokenStream>,
// idle_locals
Option<proc_macro2::TokenStream>,
// idle_resources
Option<proc_macro2::TokenStream>,
// user_idle
Option<proc_macro2::TokenStream>,
// call_idle
proc_macro2::TokenStream,
) {
2018-11-03 17:02:41 +01:00
if let Some(idle) = app.idle.as_ref() {
let mut needs_lt = false;
let mut const_app = None;
let mut idle_resources = None;
if !idle.args.resources.is_empty() {
let (item, constructor) = resources_struct(Kind::Idle, 0, &mut needs_lt, app, analysis);
idle_resources = Some(item);
const_app = Some(constructor);
}
let call_idle = quote!(idle(
idle::Locals::new(),
idle::Context::new(&rtfm::export::Priority::new(0))
));
2018-11-03 17:02:41 +01:00
let attrs = &idle.attrs;
let context = &idle.context;
let use_u32ext = if cfg!(feature = "timer-queue") {
Some(quote!(
use rtfm::U32Ext as _;
))
} else {
None
};
let (idle_locals, lets) = locals(Kind::Idle, &idle.statics);
2018-11-03 17:02:41 +01:00
let stmts = &idle.stmts;
let user_idle = quote!(
#(#attrs)*
#[allow(non_snake_case)]
fn idle(__locals: idle::Locals, #context: idle::Context) -> ! {
#use_u32ext
use rtfm::Mutex as _;
2018-11-03 17:02:41 +01:00
#(#lets;)*
#(#stmts)*
}
2018-11-03 17:02:41 +01:00
);
let mod_idle = module(
2018-11-03 17:02:41 +01:00
Kind::Idle,
(!idle.args.resources.is_empty(), needs_lt),
2018-11-03 17:02:41 +01:00
!idle.args.schedule.is_empty(),
!idle.args.spawn.is_empty(),
false,
2018-11-03 17:02:41 +01:00
app,
);
(
const_app,
Some(mod_idle),
Some(idle_locals),
idle_resources,
Some(user_idle),
call_idle,
2018-11-03 17:02:41 +01:00
)
} else {
(
None,
None,
None,
None,
None,
2018-11-03 17:02:41 +01:00
quote!(loop {
rtfm::export::wfi()
2018-11-03 17:02:41 +01:00
}),
)
}
}
/* Support functions */
/// This function creates the `Resources` struct
///
/// It's a bit unfortunate but this struct has to be created in the root because it refers to types
/// which may have been imported into the root.
fn resources_struct(
kind: Kind,
priority: u8,
needs_lt: &mut bool,
2018-11-03 17:02:41 +01:00
app: &App,
analysis: &Analysis,
) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
let mut lt = None;
2018-11-03 17:02:41 +01:00
let resources = match &kind {
Kind::Init => &app.init.args.resources,
Kind::Idle => &app.idle.as_ref().expect("UNREACHABLE").args.resources,
Kind::Interrupt(name) => &app.interrupts[name].args.resources,
Kind::Exception(name) => &app.exceptions[name].args.resources,
Kind::Task(name) => &app.tasks[name].args.resources,
};
2018-11-03 17:02:41 +01:00
let mut fields = vec![];
let mut values = vec![];
for name in resources {
let res = &app.resources[name];
2018-11-03 17:02:41 +01:00
let cfgs = &res.cfgs;
let mut_ = res.mutability;
let ty = &res.ty;
2018-11-03 17:02:41 +01:00
if kind.is_init() {
if !analysis.ownerships.contains_key(name) {
// owned by `init`
fields.push(quote!(
#(#cfgs)*
pub #name: &'static #mut_ #ty
));
2018-11-03 17:02:41 +01:00
values.push(quote!(
#(#cfgs)*
#name: &#mut_ #name
));
} else {
// owned by someone else
lt = Some(quote!('a));
2018-11-03 17:02:41 +01:00
fields.push(quote!(
#(#cfgs)*
pub #name: &'a mut #ty
));
2018-11-03 17:02:41 +01:00
values.push(quote!(
#(#cfgs)*
#name: &mut #name
));
}
} else {
let ownership = &analysis.ownerships[name];
2018-11-03 17:02:41 +01:00
let mut exclusive = false;
if ownership.needs_lock(priority) {
if mut_.is_none() {
lt = Some(quote!('a));
2019-02-16 00:22:00 +01:00
fields.push(quote!(
#(#cfgs)*
pub #name: &'a #ty
));
} else {
// resource proxy
lt = Some(quote!('a));
2018-11-03 17:02:41 +01:00
fields.push(quote!(
#(#cfgs)*
pub #name: resources::#name<'a>
));
2018-11-03 17:02:41 +01:00
values.push(quote!(
#(#cfgs)*
#name: resources::#name::new(priority)
));
continue;
}
} else {
let lt = if kind.runs_once() {
quote!('static)
} else {
lt = Some(quote!('a));
quote!('a)
};
2018-11-03 17:02:41 +01:00
if ownership.is_owned() || mut_.is_none() {
fields.push(quote!(
#(#cfgs)*
pub #name: &#lt #mut_ #ty
));
} else {
exclusive = true;
2018-11-03 17:02:41 +01:00
fields.push(quote!(
#(#cfgs)*
pub #name: rtfm::Exclusive<#lt, #ty>
));
}
}
2018-11-03 17:02:41 +01:00
let is_late = res.expr.is_none();
if is_late {
let expr = if mut_.is_some() {
quote!(&mut *#name.as_mut_ptr())
} else {
quote!(&*#name.as_ptr())
};
2018-11-03 17:02:41 +01:00
if exclusive {
values.push(quote!(
#(#cfgs)*
#name: rtfm::Exclusive(#expr)
));
} else {
values.push(quote!(
#(#cfgs)*
#name: #expr
));
}
2019-02-15 19:52:25 +01:00
} else {
if exclusive {
values.push(quote!(
#(#cfgs)*
#name: rtfm::Exclusive(&mut #name)
));
} else {
values.push(quote!(
#(#cfgs)*
#name: &#mut_ #name
));
}
}
}
}
2018-11-03 17:02:41 +01:00
if lt.is_some() {
*needs_lt = true;
2018-11-03 17:02:41 +01:00
// the struct could end up empty due to `cfg` leading to an error due to `'a` being unused
fields.push(quote!(
#[doc(hidden)]
pub __marker__: core::marker::PhantomData<&'a ()>
));
2019-02-15 19:52:25 +01:00
values.push(quote!(__marker__: core::marker::PhantomData))
}
let ident = kind.resources_ident();
let doc = format!("Resources {} has access to", ident);
let item = quote!(
#[allow(non_snake_case)]
#[doc = #doc]
pub struct #ident<#lt> {
#(#fields,)*
}
);
let arg = if kind.is_init() {
None
} else {
Some(quote!(priority: &#lt rtfm::export::Priority))
};
let constructor = quote!(
impl<#lt> #ident<#lt> {
#[inline(always)]
unsafe fn new(#arg) -> Self {
#ident {
#(#values,)*
2019-02-15 19:52:25 +01:00
}
2018-11-03 17:02:41 +01:00
}
}
);
(item, constructor)
}
2018-11-03 17:02:41 +01:00
/// Creates a `Mutex` implementation
fn impl_mutex(
app: &App,
cfgs: &[Attribute],
resources_prefix: bool,
name: &Ident,
ty: proc_macro2::TokenStream,
ceiling: u8,
ptr: proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
let path = if resources_prefix {
quote!(resources::#name)
} else {
quote!(#name)
};
2019-02-15 19:52:25 +01:00
let priority = if resources_prefix {
quote!(self.priority())
} else {
quote!(self.priority)
};
2019-02-15 19:52:25 +01:00
let device = &app.args.device;
quote!(
#(#cfgs)*
impl<'a> rtfm::Mutex for #path<'a> {
type T = #ty;
2018-11-03 17:02:41 +01:00
#[inline(always)]
fn lock<R>(&mut self, f: impl FnOnce(&mut #ty) -> R) -> R {
/// Priority ceiling
const CEILING: u8 = #ceiling;
2018-11-03 17:02:41 +01:00
unsafe {
rtfm::export::lock(
#ptr,
#priority,
CEILING,
#device::NVIC_PRIO_BITS,
f,
)
}
}
2019-02-15 19:52:25 +01:00
}
)
}
2019-02-15 19:52:25 +01:00
/// Creates a `Locals` struct and related code. This returns
///
/// - `locals`
///
/// ```
/// pub struct Locals<'a> {
/// #[cfg(never)]
/// pub X: &'a mut X,
/// __marker__: PhantomData<&'a mut ()>,
/// }
/// ```
///
/// - `lt`
///
/// ```
/// 'a
/// ```
///
/// - `lets`
///
/// ```
/// #[cfg(never)]
/// let X = __locals.X
/// ```
fn locals(
kind: Kind,
statics: &BTreeMap<Ident, Static>,
) -> (
// locals
proc_macro2::TokenStream,
// lets
Vec<proc_macro2::TokenStream>,
) {
let runs_once = kind.runs_once();
let ident = kind.locals_ident();
2018-11-03 17:02:41 +01:00
let mut lt = None;
let mut fields = vec![];
let mut lets = vec![];
let mut items = vec![];
let mut values = vec![];
for (name, static_) in statics {
let lt = if runs_once {
quote!('static)
} else {
lt = Some(quote!('a));
quote!('a)
};
let cfgs = &static_.cfgs;
let expr = &static_.expr;
let ty = &static_.ty;
fields.push(quote!(
#(#cfgs)*
#name: &#lt mut #ty
2018-11-03 17:02:41 +01:00
));
items.push(quote!(
#(#cfgs)*
static mut #name: #ty = #expr
));
values.push(quote!(
#(#cfgs)*
#name: &mut #name
));
lets.push(quote!(
#(#cfgs)*
let #name = __locals.#name
));
}
if lt.is_some() {
fields.push(quote!(__marker__: core::marker::PhantomData<&'a mut ()>));
values.push(quote!(__marker__: core::marker::PhantomData));
}
let locals = quote!(
#[allow(non_snake_case)]
#[doc(hidden)]
pub struct #ident<#lt> {
#(#fields),*
}
2019-02-16 00:22:00 +01:00
impl<#lt> #ident<#lt> {
#[inline(always)]
unsafe fn new() -> Self {
#(#items;)*
#ident {
#(#values),*
2019-02-16 00:22:00 +01:00
}
}
}
);
2018-11-03 17:02:41 +01:00
(locals, lets)
2018-11-03 17:02:41 +01:00
}
/// This function creates a module that contains
//
// - the Context struct
// - a re-export of the ${name}Resources struct (maybe)
// - a re-export of the ${name}LateResources struct (maybe)
// - a re-export of the ${name}Locals struct
// - the Spawn struct (maybe)
// - the Schedule struct (maybe, if `timer-queue` is enabled)
fn module(
kind: Kind,
resources: (/* has */ bool, /* 'a */ bool),
schedule: bool,
spawn: bool,
late_resources: bool,
2018-11-03 17:02:41 +01:00
app: &App,
) -> proc_macro2::TokenStream {
let mut items = vec![];
let mut fields = vec![];
let mut values = vec![];
2018-11-03 17:02:41 +01:00
let name = kind.ident();
2018-11-03 17:02:41 +01:00
let mut needs_instant = false;
let mut lt = None;
match kind {
Kind::Init => {
if cfg!(feature = "timer-queue") {
fields.push(quote!(
/// System start time = `Instant(0 /* cycles */)`
pub start: rtfm::Instant
));
values.push(quote!(start: rtfm::Instant::artificial(0)));
}
2019-02-15 19:52:25 +01:00
let device = &app.args.device;
fields.push(quote!(
/// Core (Cortex-M) peripherals
pub core: rtfm::Peripherals<'a>
2019-02-15 19:52:25 +01:00
));
fields.push(quote!(
/// Device specific peripherals
pub device: #device::Peripherals
2019-02-15 19:52:25 +01:00
));
2018-11-03 17:02:41 +01:00
values.push(quote!(core));
values.push(quote!(device: #device::Peripherals::steal()));
lt = Some(quote!('a));
}
2018-11-03 17:02:41 +01:00
Kind::Idle => {}
2019-02-15 19:52:25 +01:00
Kind::Exception(_) | Kind::Interrupt(_) => {
if cfg!(feature = "timer-queue") {
fields.push(quote!(
/// Time at which this handler started executing
pub start: rtfm::Instant
));
2018-11-03 17:02:41 +01:00
values.push(quote!(start: instant));
2019-02-15 19:52:25 +01:00
needs_instant = true;
}
}
2018-11-03 17:02:41 +01:00
Kind::Task(_) => {
if cfg!(feature = "timer-queue") {
fields.push(quote!(
/// The time at which this task was scheduled to run
pub scheduled: rtfm::Instant
));
2018-11-03 17:02:41 +01:00
values.push(quote!(scheduled: instant));
needs_instant = true;
2018-11-03 17:02:41 +01:00
}
}
2018-11-03 17:02:41 +01:00
}
let ident = kind.locals_ident();
items.push(quote!(
#[doc(inline)]
pub use super::#ident as Locals;
));
2018-11-03 17:02:41 +01:00
if resources.0 {
let ident = kind.resources_ident();
let lt = if resources.1 {
lt = Some(quote!('a));
Some(quote!('a))
} else {
None
2018-11-03 17:02:41 +01:00
};
items.push(quote!(
#[doc(inline)]
pub use super::#ident as Resources;
));
fields.push(quote!(
/// Resources this task has access to
pub resources: Resources<#lt>
));
2018-11-03 17:02:41 +01:00
let priority = if kind.is_init() {
None
2019-02-15 19:52:25 +01:00
} else {
Some(quote!(priority))
2019-02-15 19:52:25 +01:00
};
values.push(quote!(resources: Resources::new(#priority)));
}
2018-11-03 17:02:41 +01:00
if schedule {
let doc = "Tasks that can be `schedule`-d from this context";
if kind.is_init() {
items.push(quote!(
#[doc = #doc]
#[derive(Clone, Copy)]
pub struct Schedule {
_not_send: core::marker::PhantomData<*mut ()>,
}
));
2018-11-03 17:02:41 +01:00
fields.push(quote!(
#[doc = #doc]
pub schedule: Schedule
));
2018-11-03 17:02:41 +01:00
values.push(quote!(
schedule: Schedule { _not_send: core::marker::PhantomData }
));
} else {
lt = Some(quote!('a));
2018-11-03 17:02:41 +01:00
items.push(quote!(
#[doc = #doc]
#[derive(Clone, Copy)]
pub struct Schedule<'a> {
priority: &'a rtfm::export::Priority,
2018-11-03 17:02:41 +01:00
}
impl<'a> Schedule<'a> {
#[doc(hidden)]
#[inline(always)]
pub unsafe fn priority(&self) -> &rtfm::export::Priority {
&self.priority
2018-11-03 17:02:41 +01:00
}
}
));
fields.push(quote!(
#[doc = #doc]
pub schedule: Schedule<'a>
));
values.push(quote!(
schedule: Schedule { priority }
));
}
2018-11-03 17:02:41 +01:00
}
if spawn {
let doc = "Tasks that can be `spawn`-ed from this context";
if kind.is_init() {
fields.push(quote!(
#[doc = #doc]
pub spawn: Spawn
));
2018-11-03 17:02:41 +01:00
items.push(quote!(
#[doc = #doc]
#[derive(Clone, Copy)]
pub struct Spawn {
_not_send: core::marker::PhantomData<*mut ()>,
}
));
2018-11-03 17:02:41 +01:00
values.push(quote!(spawn: Spawn { _not_send: core::marker::PhantomData }));
2019-02-15 19:52:25 +01:00
} else {
lt = Some(quote!('a));
2019-02-15 19:52:25 +01:00
fields.push(quote!(
#[doc = #doc]
pub spawn: Spawn<'a>
));
let mut instant_method = None;
if kind.is_idle() {
items.push(quote!(
#[doc = #doc]
#[derive(Clone, Copy)]
pub struct Spawn<'a> {
priority: &'a rtfm::export::Priority,
}
));
values.push(quote!(spawn: Spawn { priority }));
} else {
let instant_field = if cfg!(feature = "timer-queue") {
needs_instant = true;
instant_method = Some(quote!(
pub unsafe fn instant(&self) -> rtfm::Instant {
self.instant
}
));
Some(quote!(instant: rtfm::Instant,))
} else {
None
};
2018-11-03 17:02:41 +01:00
items.push(quote!(
/// Tasks that can be spawned from this context
#[derive(Clone, Copy)]
pub struct Spawn<'a> {
#instant_field
priority: &'a rtfm::export::Priority,
}
));
2018-11-03 17:02:41 +01:00
let _instant = if needs_instant {
Some(quote!(, instant))
2018-11-03 17:02:41 +01:00
} else {
None
};
values.push(quote!(
spawn: Spawn { priority #_instant }
));
2018-11-03 17:02:41 +01:00
}
items.push(quote!(
impl<'a> Spawn<'a> {
#[doc(hidden)]
#[inline(always)]
pub unsafe fn priority(&self) -> &rtfm::export::Priority {
self.priority
}
2018-11-03 17:02:41 +01:00
#instant_method
2018-11-03 17:02:41 +01:00
}
));
}
}
if late_resources {
2019-02-15 19:52:25 +01:00
items.push(quote!(
#[doc(inline)]
pub use super::initLateResources as LateResources;
2019-02-15 19:52:25 +01:00
));
}
2018-11-03 17:02:41 +01:00
let doc = match kind {
Kind::Exception(_) => "Hardware task (exception)",
Kind::Idle => "Idle loop",
Kind::Init => "Initialization function",
Kind::Interrupt(_) => "Hardware task (interrupt)",
Kind::Task(_) => "Software task",
};
2018-11-03 17:02:41 +01:00
let core = if kind.is_init() {
lt = Some(quote!('a));
Some(quote!(core: rtfm::Peripherals<'a>,))
} else {
None
};
2018-11-03 17:02:41 +01:00
let priority = if kind.is_init() {
None
} else {
Some(quote!(priority: &#lt rtfm::export::Priority))
};
2018-11-03 17:02:41 +01:00
let instant = if needs_instant {
Some(quote!(, instant: rtfm::Instant))
} else {
None
};
2018-11-03 17:02:41 +01:00
items.push(quote!(
/// Execution context
pub struct Context<#lt> {
#(#fields,)*
}
2018-11-03 17:02:41 +01:00
impl<#lt> Context<#lt> {
#[inline(always)]
pub unsafe fn new(#core #priority #instant) -> Self {
Context {
#(#values,)*
}
}
2018-11-03 17:02:41 +01:00
}
));
if !items.is_empty() {
quote!(
#[allow(non_snake_case)]
#[doc = #doc]
pub mod #name {
#(#items)*
}
)
} else {
quote!()
}
2018-11-03 17:02:41 +01:00
}
/// Creates the body of `spawn_${name}`
fn mk_spawn_body<'a>(
spawner: &Ident,
name: &Ident,
app: &'a App,
analysis: &Analysis,
) -> proc_macro2::TokenStream {
let spawner_is_init = spawner == "init";
let device = &app.args.device;
2018-11-03 17:02:41 +01:00
let spawnee = &app.tasks[name];
let priority = spawnee.args.priority;
let dispatcher = &analysis.dispatchers[&priority].interrupt;
2018-11-03 17:02:41 +01:00
let (_, tupled, _, _) = regroup_inputs(&spawnee.inputs);
2018-11-03 17:02:41 +01:00
let inputs = mk_inputs_ident(name);
let fq = mk_fq_ident(name);
2018-11-03 17:02:41 +01:00
let rq = mk_rq_ident(priority);
let t = mk_t_ident(priority);
2018-11-03 17:02:41 +01:00
let write_instant = if cfg!(feature = "timer-queue") {
let instants = mk_instants_ident(name);
2018-11-03 17:02:41 +01:00
Some(quote!(
#instants.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(instant);
2018-11-03 17:02:41 +01:00
))
} else {
None
};
2019-04-21 18:20:57 +02:00
let (dequeue, enqueue) = if spawner_is_init {
// `init` has exclusive access to these queues so we can bypass the resources AND
// the consumer / producer split
if cfg!(feature = "nightly") {
(
quote!(#fq.dequeue()),
quote!(#rq.enqueue_unchecked((#t::#name, index));),
)
} else {
(
quote!((*#fq.as_mut_ptr()).dequeue()),
quote!((*#rq.as_mut_ptr()).enqueue_unchecked((#t::#name, index));),
)
}
} else {
(
quote!((#fq { priority }).lock(|fq| fq.split().1.dequeue())),
quote!((#rq { priority }).lock(|rq| {
rq.split().0.enqueue_unchecked((#t::#name, index))
});),
)
};
2019-04-21 18:20:57 +02:00
quote!(
unsafe {
use rtfm::Mutex as _;
let input = #tupled;
if let Some(index) = #dequeue {
#inputs.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(input);
2019-04-21 18:20:57 +02:00
#write_instant
2019-04-21 18:20:57 +02:00
#enqueue
rtfm::pend(#device::Interrupt::#dispatcher);
2018-11-03 17:02:41 +01:00
Ok(())
} else {
Err(input)
}
}
2018-11-03 17:02:41 +01:00
)
}
/// Creates the body of `schedule_${name}`
fn mk_schedule_body<'a>(scheduler: &Ident, name: &Ident, app: &'a App) -> proc_macro2::TokenStream {
let scheduler_is_init = scheduler == "init";
2018-11-03 17:02:41 +01:00
let schedulee = &app.tasks[name];
2018-11-03 17:02:41 +01:00
let (_, tupled, _, _) = regroup_inputs(&schedulee.inputs);
2018-11-03 17:02:41 +01:00
let fq = mk_fq_ident(name);
let inputs = mk_inputs_ident(name);
let instants = mk_instants_ident(name);
2018-11-03 17:02:41 +01:00
let (dequeue, enqueue) = if scheduler_is_init {
// `init` has exclusive access to these queues so we can bypass the resources AND
// the consumer / producer split
let dequeue = if cfg!(feature = "nightly") {
quote!(#fq.dequeue())
} else {
quote!((*#fq.as_mut_ptr()).dequeue())
};
2018-11-03 17:02:41 +01:00
(dequeue, quote!((*TQ.as_mut_ptr()).enqueue_unchecked(nr);))
} else {
(
quote!((#fq { priority }).lock(|fq| fq.split().1.dequeue())),
quote!((TQ { priority }).lock(|tq| tq.enqueue_unchecked(nr));),
)
};
2018-11-03 17:02:41 +01:00
quote!(
unsafe {
use rtfm::Mutex as _;
2018-11-03 17:02:41 +01:00
let input = #tupled;
if let Some(index) = #dequeue {
#instants.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(instant);
2018-11-03 17:02:41 +01:00
#inputs.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(input);
2018-11-03 17:02:41 +01:00
let nr = rtfm::export::NotReady {
instant,
index,
task: T::#name,
};
2018-11-03 17:02:41 +01:00
#enqueue
2018-11-03 17:02:41 +01:00
Ok(())
} else {
Err(input)
2018-11-03 17:02:41 +01:00
}
}
)
2018-11-03 17:02:41 +01:00
}
/// `u8` -> (unsuffixed) `LitInt`
2018-11-03 17:02:41 +01:00
fn mk_capacity_literal(capacity: u8) -> LitInt {
LitInt::new(u64::from(capacity), IntSuffix::None, Span::call_site())
}
/// e.g. `4u8` -> `U4`
2018-11-03 17:02:41 +01:00
fn mk_typenum_capacity(capacity: u8, power_of_two: bool) -> proc_macro2::TokenStream {
let capacity = if power_of_two {
capacity
.checked_next_power_of_two()
.expect("capacity.next_power_of_two()")
} else {
capacity
};
let ident = Ident::new(&format!("U{}", capacity), Span::call_site());
quote!(rtfm::export::consts::#ident)
}
/// e.g. `foo` -> `foo_INPUTS`
fn mk_inputs_ident(base: &Ident) -> Ident {
Ident::new(&format!("{}_INPUTS", base), Span::call_site())
2019-01-17 02:36:55 +01:00
}
2018-11-03 17:02:41 +01:00
/// e.g. `foo` -> `foo_INSTANTS`
fn mk_instants_ident(base: &Ident) -> Ident {
Ident::new(&format!("{}_INSTANTS", base), Span::call_site())
2018-11-03 17:02:41 +01:00
}
/// e.g. `foo` -> `foo_FQ`
fn mk_fq_ident(base: &Ident) -> Ident {
Ident::new(&format!("{}_FQ", base), Span::call_site())
}
2018-11-03 17:02:41 +01:00
/// e.g. `3` -> `RQ3`
fn mk_rq_ident(level: u8) -> Ident {
Ident::new(&format!("RQ{}", level), Span::call_site())
}
2018-11-03 17:02:41 +01:00
/// e.g. `3` -> `T3`
fn mk_t_ident(level: u8) -> Ident {
Ident::new(&format!("T{}", level), Span::call_site())
2018-11-03 17:02:41 +01:00
}
fn mk_spawn_ident(task: &Ident) -> Ident {
Ident::new(&format!("spawn_{}", task), Span::call_site())
}
2018-11-03 17:02:41 +01:00
fn mk_schedule_ident(task: &Ident) -> Ident {
Ident::new(&format!("schedule_{}", task), Span::call_site())
2018-11-03 17:02:41 +01:00
}
// Regroups a task inputs
//
// e.g. &[`input: Foo`], &[`mut x: i32`, `ref y: i64`]
fn regroup_inputs(
inputs: &[ArgCaptured],
) -> (
// args e.g. &[`_0`], &[`_0: i32`, `_1: i64`]
Vec<proc_macro2::TokenStream>,
// tupled e.g. `_0`, `(_0, _1)`
proc_macro2::TokenStream,
// untupled e.g. &[`_0`], &[`_0`, `_1`]
Vec<proc_macro2::TokenStream>,
// ty e.g. `Foo`, `(i32, i64)`
proc_macro2::TokenStream,
) {
2018-11-03 17:02:41 +01:00
if inputs.len() == 1 {
let ty = &inputs[0].ty;
(
vec![quote!(_0: #ty)],
quote!(_0),
vec![quote!(_0)],
quote!(#ty),
)
2018-11-03 17:02:41 +01:00
} 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));
2018-11-03 17:02:41 +01:00
pats.push(quote!(#i));
tys.push(quote!(#ty));
}
let tupled = {
let pats = pats.clone();
quote!((#(#pats,)*))
};
let ty = quote!((#(#tys,)*));
(args, tupled, pats, ty)
2018-11-03 17:02:41 +01:00
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
2018-11-03 17:02:41 +01:00
enum Kind {
Exception(Ident),
Idle,
Init,
Interrupt(Ident),
Task(Ident),
}
impl Kind {
fn ident(&self) -> Ident {
let span = Span::call_site();
2018-11-03 17:02:41 +01:00
match self {
Kind::Init => Ident::new("init", span),
Kind::Idle => Ident::new("idle", span),
2018-11-03 17:02:41 +01:00
Kind::Task(name) | Kind::Interrupt(name) | Kind::Exception(name) => name.clone(),
}
}
fn locals_ident(&self) -> Ident {
Ident::new(&format!("{}Locals", self.ident()), Span::call_site())
}
fn resources_ident(&self) -> Ident {
Ident::new(&format!("{}Resources", self.ident()), Span::call_site())
}
2018-11-03 17:02:41 +01:00
fn is_idle(&self) -> bool {
*self == Kind::Idle
}
fn is_init(&self) -> bool {
*self == Kind::Init
}
fn runs_once(&self) -> bool {
match *self {
Kind::Init | Kind::Idle => true,
_ => false,
}
}
}