diff --git a/examples/generator.rs b/examples/generator.rs new file mode 100644 index 0000000000..2808348077 --- /dev/null +++ b/examples/generator.rs @@ -0,0 +1,61 @@ +//! examples/hardware.rs + +#![feature(generator_trait)] +#![feature(generators)] +#![feature(never_type)] +#![feature(type_alias_impl_trait)] +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use cortex_m_semihosting::{debug, hprintln}; +use lm3s6965::Interrupt; +use panic_semihosting as _; + +#[rtfm::app(device = lm3s6965)] +const APP: () = { + struct Resources { + #[init(0)] + x: i64, + } + + #[init] + fn init(_: init::Context) { + hprintln!("init").ok(); + } + + #[idle] + fn idle(_: idle::Context) -> ! { + hprintln!("idle").ok(); + + rtfm::pend(Interrupt::UART0); + hprintln!("C").ok(); + rtfm::pend(Interrupt::UART0); + hprintln!("E").ok(); + debug::exit(debug::EXIT_SUCCESS); + + loop {} + } + + #[task(binds = UART0, priority = 1, resources = [x])] + fn uart0(mut cx: uart0::Context) -> impl Generator { + hprintln!("A").ok(); + + move || loop { + hprintln!("B").ok(); + yield; + + cx.resources.x.lock(|x| { + hprintln!("lock").ok(); + *x += 1; + }); + + hprintln!("D").ok(); + yield; + } + } + + #[task(binds = UART1, priority = 2, resources = [x])] + fn uart1(_: uart1::Context) {} +}; diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 465980382c..9169bf5c09 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -21,7 +21,8 @@ proc-macro = true proc-macro2 = "1" quote = "1" syn = "1" -rtfm-syntax = "0.4.0-beta.2" +# rtfm-syntax = "0.4.0-beta.2" +rtfm-syntax = { git = "https://github.com/rtfm-rs/rtfm-syntax", branch = "impl-generator" } [features] heterogeneous = [] diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs index 0213848157..9439467bf0 100644 --- a/macros/src/codegen.rs +++ b/macros/src/codegen.rs @@ -39,7 +39,8 @@ pub fn app(app: &App, analysis: &Analysis, extra: &Extra) -> TokenStream2 { let (const_app_init, root_init, user_init, call_init) = init::codegen(core, app, analysis, extra); - let (const_app_post_init, post_init_stmts) = post_init::codegen(core, analysis, extra); + let (const_app_post_init, root_post_init, post_init_stmts) = + post_init::codegen(core, &app, analysis, extra); let (const_app_idle, root_idle, user_idle, call_idle) = idle::codegen(core, app, analysis, extra); @@ -53,6 +54,8 @@ pub fn app(app: &App, analysis: &Analysis, extra: &Extra) -> TokenStream2 { root.push(quote!( #(#root_init)* + #(#root_post_init)* + #(#root_idle)* )); @@ -87,7 +90,8 @@ pub fn app(app: &App, analysis: &Analysis, extra: &Extra) -> TokenStream2 { )); } - let (const_app_resources, mod_resources) = resources::codegen(app, analysis, extra); + let (const_app_resources, mod_resources, mod_gresources) = + resources::codegen(app, analysis, extra); let (const_app_hardware_tasks, root_hardware_tasks, user_hardware_tasks) = hardware_tasks::codegen(app, analysis, extra); @@ -128,6 +132,8 @@ pub fn app(app: &App, analysis: &Analysis, extra: &Extra) -> TokenStream2 { #mod_resources + #mod_gresources + #(#root_hardware_tasks)* #(#root_software_tasks)* diff --git a/macros/src/codegen/hardware_tasks.rs b/macros/src/codegen/hardware_tasks.rs index a9c2a2bdc7..81f0b62f6b 100644 --- a/macros/src/codegen/hardware_tasks.rs +++ b/macros/src/codegen/hardware_tasks.rs @@ -29,6 +29,46 @@ pub fn codegen( let mut user_tasks = vec![]; for (name, task) in &app.hardware_tasks { + // TODO split this big conditional to reuse code below + if task.is_generator { + let symbol = task.args.binds.clone(); + let gen_i = util::generator_ident(&name.to_string()); + const_app.push(quote!( + #[allow(non_snake_case)] + #[no_mangle] + unsafe fn #symbol() { + use core::ops::Generator; + + core::pin::Pin::new_unchecked(&mut *#gen_i.as_mut_ptr()).resume(); + } + )); + + let priority = task.args.priority; + // `${task}Resources` + if !task.args.resources.is_empty() { + let (item, constructor) = resources_struct::codegen( + Context::HardwareTask(name), + priority, + &mut false, + app, + analysis, + ); + + root.push(item); + + const_app.push(constructor); + } + + root.push(module::codegen( + Context::HardwareTask(name), + false, + app, + extra, + )); + + continue; + } + let core = task.args.core; let cfg_core = util::cfg_core(core, app.args.cores); diff --git a/macros/src/codegen/locals.rs b/macros/src/codegen/locals.rs index cbfe05fbb9..1758c8e73c 100644 --- a/macros/src/codegen/locals.rs +++ b/macros/src/codegen/locals.rs @@ -20,7 +20,7 @@ pub fn codegen( ) { assert!(!locals.is_empty()); - let runs_once = ctxt.runs_once(); + let runs_once = ctxt.runs_once(app); let ident = util::locals_ident(ctxt, app); let mut lt = None; diff --git a/macros/src/codegen/module.rs b/macros/src/codegen/module.rs index 5f077a2200..3634fcc047 100644 --- a/macros/src/codegen/module.rs +++ b/macros/src/codegen/module.rs @@ -111,7 +111,7 @@ pub fn codegen(ctxt: Context, resources_tick: bool, app: &App, extra: &Extra) -> pub resources: Resources<#lt> )); - let priority = if ctxt.is_init() { + let priority = if ctxt.is_init() || ctxt.is_generator(app) { None } else { Some(quote!(priority)) @@ -281,7 +281,7 @@ pub fn codegen(ctxt: Context, resources_tick: bool, app: &App, extra: &Extra) -> None }; - let priority = if ctxt.is_init() { + let priority = if ctxt.is_init() || ctxt.is_generator(app) { None } else { Some(quote!(priority: &#lt rtfm::export::Priority)) diff --git a/macros/src/codegen/post_init.rs b/macros/src/codegen/post_init.rs index 19773e45bd..d4b131814f 100644 --- a/macros/src/codegen/post_init.rs +++ b/macros/src/codegen/post_init.rs @@ -1,15 +1,29 @@ use proc_macro2::TokenStream as TokenStream2; use quote::quote; +use rtfm_syntax::{ast::App, Context}; -use crate::{analyze::Analysis, check::Extra, codegen::util}; +use crate::{ + analyze::Analysis, + check::Extra, + codegen::{locals, util}, +}; /// Generates code that runs after `#[init]` returns pub fn codegen( core: u8, + app: &App, analysis: &Analysis, extra: &Extra, -) -> (Vec, Vec) { +) -> ( + // const_app + Vec, + // root + Vec, + // stmts + Vec, +) { let mut const_app = vec![]; + let mut root = vec![]; let mut stmts = vec![]; // initialize late resources @@ -22,6 +36,48 @@ pub fn codegen( } } + // TODO WIP + for (name, task) in &app.hardware_tasks { + if task.is_generator { + let name_s = name.to_string(); + let gen_i = util::generator_ident(&name_s); + let gen_t = util::generator_type(&name_s); + const_app.push(quote!( + static mut #gen_i: core::mem::MaybeUninit<#gen_t> = + core::mem::MaybeUninit::uninit(); + )); + + let (locals_pat, locals_new) = if task.locals.is_empty() { + (None, quote!()) + } else { + let (struct_, pat) = + locals::codegen(Context::HardwareTask(name), &task.locals, core, app); + + root.push(struct_); + + (Some(pat), quote!(#name::Locals::new(),)) + }; + + let context = &task.context; + let task_stmts = &task.stmts; + let locals_pat = locals_pat.iter(); + root.push(quote!( + type #gen_t = impl core::ops::Generator; + + // #[allow(non_snake_case)] + fn #name(#(#locals_pat,)* #context: #name::Context) -> #gen_t { + use rtfm::Mutex as _; + + #(#task_stmts)* + } + )); + + stmts.push(quote!( + #gen_i.as_mut_ptr().write(#name(#locals_new #name::Context::new())); + )); + } + } + if analysis.timer_queues.is_empty() { // cross-initialization barriers -- notify *other* cores that their resources have been // initialized @@ -151,5 +207,5 @@ pub fn codegen( // enable the interrupts -- this completes the `init`-ialization phase stmts.push(quote!(rtfm::export::interrupt::enable();)); - (const_app, stmts) + (const_app, root, stmts) } diff --git a/macros/src/codegen/resources.rs b/macros/src/codegen/resources.rs index 8cb788d1ae..1af95ada8b 100644 --- a/macros/src/codegen/resources.rs +++ b/macros/src/codegen/resources.rs @@ -17,9 +17,12 @@ pub fn codegen( Vec, // mod_resources -- the `resources` module TokenStream2, + // mod_gresources -- the `gresources` module + TokenStream2, ) { let mut const_app = vec![]; let mut mod_resources = vec![]; + let mut mod_gresources = vec![]; for (name, res, expr, loc) in app.resources(analysis) { let cfgs = &res.cfgs; @@ -77,6 +80,8 @@ pub fn codegen( if let Some(Ownership::Contended { ceiling }) = analysis.ownerships.get(name) { let cfg_core = util::cfg_core(loc.core().expect("UNREACHABLE"), app.args.cores); + // TODO generate less code -- we don't always need both `gresources::foo` and + // `resources::foo` mod_resources.push(quote!( #[allow(non_camel_case_types)] #(#cfgs)* @@ -114,8 +119,37 @@ pub fn codegen( name, quote!(#ty), *ceiling, + ptr.clone(), + )); + + mod_gresources.push(quote!( + #[allow(non_camel_case_types)] + #(#cfgs)* + #cfg_core + pub struct #name { + _not_send_or_sync: core::marker::PhantomData<*mut ()>, + } + + #(#cfgs)* + #cfg_core + impl #name { + #[inline(always)] + pub unsafe fn new() -> Self { + #name { _not_send_or_sync: core::marker::PhantomData } + } + } + )); + + const_app.push(util::impl_gmutex( + extra, + cfgs, + cfg_core.as_ref(), + name, + quote!(#ty), + *ceiling, ptr, )); + } } @@ -129,5 +163,13 @@ pub fn codegen( }) }; - (const_app, mod_resources) + let mod_gresources = if mod_gresources.is_empty() { + quote!() + } else { + quote!(mod gresources { + #(#mod_gresources)* + }) + }; + + (const_app, mod_resources, mod_gresources) } diff --git a/macros/src/codegen/resources_struct.rs b/macros/src/codegen/resources_struct.rs index 07a60616fa..17503a6647 100644 --- a/macros/src/codegen/resources_struct.rs +++ b/macros/src/codegen/resources_struct.rs @@ -4,6 +4,7 @@ use rtfm_syntax::{ast::App, Context}; use crate::{analyze::Analysis, codegen::util}; +// TODO need to do something different when generators are involved pub fn codegen( ctxt: Context, priority: u8, @@ -63,6 +64,67 @@ pub fn codegen( #name: &mut #name )); } + } else if ctxt.is_generator(app) { + let ownership = &analysis.ownerships[name]; + + if ownership.needs_lock(priority) { + if mut_.is_none() { + // mod gresourcesd + unimplemented!() + } else { + // resource proxy + fields.push(quote!( + #(#cfgs)* + pub #name: gresources::#name + )); + + values.push(quote!( + #(#cfgs)* + #name: gresources::#name::new() + + )); + + continue; + } + } else { + let lt = if ctxt.runs_once(app) { + quote!('static) + } else { + lt = Some(quote!('a)); + quote!('a) + }; + + if ownership.is_owned() || mut_.is_none() { + fields.push(quote!( + #(#cfgs)* + pub #name: &#lt #mut_ #ty + )); + } else { + fields.push(quote!( + #(#cfgs)* + pub #name: &#lt mut #ty + )); + } + } + + let is_late = expr.is_none(); + if is_late { + let expr = if mut_.is_some() { + quote!(&mut *#name.as_mut_ptr()) + } else { + quote!(&*#name.as_ptr()) + }; + + values.push(quote!( + #(#cfgs)* + #name: #expr + )); + } else { + values.push(quote!( + #(#cfgs)* + #name: &#mut_ #name + )); + } } else { let ownership = &analysis.ownerships[name]; @@ -92,7 +154,7 @@ pub fn codegen( continue; } } else { - let lt = if ctxt.runs_once() { + let lt = if ctxt.runs_once(app) { quote!('static) } else { lt = Some(quote!('a)); @@ -161,7 +223,7 @@ pub fn codegen( } ); - let arg = if ctxt.is_init() { + let arg = if ctxt.is_init() || ctxt.is_generator(app) { None } else { Some(quote!(priority: &#lt rtfm::export::Priority)) diff --git a/macros/src/codegen/util.rs b/macros/src/codegen/util.rs index 207272dc90..3ee55bfab8 100644 --- a/macros/src/codegen/util.rs +++ b/macros/src/codegen/util.rs @@ -91,6 +91,43 @@ pub fn impl_mutex( ) } +/// Generates a `Mutex` implementation for a resource seen from a generator task +pub fn impl_gmutex( + extra: &Extra, + cfgs: &[Attribute], + cfg_core: Option<&TokenStream2>, + name: &Ident, + ty: TokenStream2, + ceiling: u8, + ptr: TokenStream2, +) -> TokenStream2 { + let path = quote!(gresources::#name); + + let device = extra.device; + quote!( + #(#cfgs)* + #cfg_core + impl rtfm::Mutex for #path { + type T = #ty; + + #[inline(always)] + fn lock(&mut self, f: impl FnOnce(&mut #ty) -> R) -> R { + /// Priority ceiling + const CEILING: u8 = #ceiling; + + unsafe { + rtfm::export::glock( + #ptr, + CEILING, + #device::NVIC_PRIO_BITS, + f, + ) + } + } + } + ) +} + /// Generates an identifier for a cross-initialization barrier pub fn init_barrier(initializer: Core) -> Ident { Ident::new(&format!("IB{}", initializer), Span::call_site()) @@ -323,3 +360,11 @@ pub fn suffixed(name: &str, core: u8) -> Ident { pub fn tq_ident(core: Core) -> Ident { Ident::new(&format!("TQ{}", core), Span::call_site()) } + +pub fn generator_ident(task: &str) -> Ident { + Ident::new(&format!("{}S", task), Span::call_site()) +} + +pub fn generator_type(task: &str) -> Ident { + Ident::new(&format!("{}T", task), Span::call_site()) +} diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 54282e1ec8..9bace0862c 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -209,6 +209,7 @@ pub fn app(args: TokenStream, input: TokenStream) -> TokenStream { settings.parse_cores = cfg!(feature = "heterogeneous") || cfg!(feature = "homogeneous"); settings.parse_extern_interrupt = true; settings.parse_schedule = true; + settings.parse_impl_generator = true; let (app, analysis) = match rtfm_syntax::parse(args, input, settings) { Err(e) => return e.to_compile_error().into(), diff --git a/src/export.rs b/src/export.rs index 96c444bf46..44eb534887 100644 --- a/src/export.rs +++ b/src/export.rs @@ -5,7 +5,7 @@ use core::{ pub use crate::tq::{NotReady, TimerQueue}; #[cfg(armv7m)] -pub use cortex_m::register::basepri; +pub use cortex_m::register::{basepri, basepri_max}; pub use cortex_m::{ asm::wfi, interrupt, @@ -145,6 +145,25 @@ pub unsafe fn lock( } } +#[cfg(armv7m)] +#[inline(always)] +pub unsafe fn glock( + ptr: *mut T, + ceiling: u8, + nvic_prio_bits: u8, + f: impl FnOnce(&mut T) -> R, +) -> R { + if ceiling == (1 << nvic_prio_bits) { + interrupt::free(|_| f(&mut *ptr)) + } else { + let current = basepri::read(); + basepri_max::write(logical2hw(ceiling, nvic_prio_bits)); + let r = f(&mut *ptr); + basepri::write(logical2hw(current, nvic_prio_bits)); + r + } +} + #[cfg(not(armv7m))] #[inline(always)] pub unsafe fn lock( @@ -166,6 +185,8 @@ pub unsafe fn lock( } } +// TODO glock for ARMv6-M + #[inline] pub fn logical2hw(logical: u8, nvic_prio_bits: u8) -> u8 { ((1 << nvic_prio_bits) - logical) << (8 - nvic_prio_bits)