diff --git a/embassy-macros/src/chip/nrf.rs b/embassy-macros/src/chip/nrf.rs deleted file mode 100644 index 3ff6a74c..00000000 --- a/embassy-macros/src/chip/nrf.rs +++ /dev/null @@ -1,11 +0,0 @@ -use crate::path::ModulePrefix; -use proc_macro2::TokenStream; -use quote::quote; - -pub fn generate(embassy_prefix: &ModulePrefix, config: syn::Expr) -> TokenStream { - let embassy_nrf_path = embassy_prefix.append("embassy_nrf").path(); - - quote!( - let p = #embassy_nrf_path::init(#config); - ) -} diff --git a/embassy-macros/src/chip/rp.rs b/embassy-macros/src/chip/rp.rs deleted file mode 100644 index ba0a97ad..00000000 --- a/embassy-macros/src/chip/rp.rs +++ /dev/null @@ -1,10 +0,0 @@ -use crate::path::ModulePrefix; -use proc_macro2::TokenStream; -use quote::quote; - -pub fn generate(embassy_prefix: &ModulePrefix, config: syn::Expr) -> TokenStream { - let embassy_rp_path = embassy_prefix.append("embassy_rp").path(); - quote!( - let p = #embassy_rp_path::init(#config); - ) -} diff --git a/embassy-macros/src/chip/stm32.rs b/embassy-macros/src/chip/stm32.rs deleted file mode 100644 index c6938836..00000000 --- a/embassy-macros/src/chip/stm32.rs +++ /dev/null @@ -1,11 +0,0 @@ -use crate::path::ModulePrefix; -use proc_macro2::TokenStream; -use quote::quote; - -pub fn generate(embassy_prefix: &ModulePrefix, config: syn::Expr) -> TokenStream { - let embassy_stm32_path = embassy_prefix.append("embassy_stm32").path(); - - quote!( - let p = #embassy_stm32_path::init(#config); - ) -} diff --git a/embassy-macros/src/lib.rs b/embassy-macros/src/lib.rs index 44a8d3b9..085f7889 100644 --- a/embassy-macros/src/lib.rs +++ b/embassy-macros/src/lib.rs @@ -1,228 +1,39 @@ -#![feature(proc_macro_diagnostic)] - extern crate proc_macro; -use darling::FromMeta; use proc_macro::TokenStream; -use proc_macro2::Span; -use quote::{format_ident, quote}; -use std::iter; -use syn::spanned::Spanned; -use syn::{parse, Type, Visibility}; -use syn::{ItemFn, ReturnType}; -mod path; - -use path::ModulePrefix; - -#[derive(Debug, FromMeta)] -struct TaskArgs { - #[darling(default)] - pool_size: Option, - #[darling(default)] - send: bool, - #[darling(default)] - embassy_prefix: ModulePrefix, -} +mod macros; +mod util; +use macros::*; #[proc_macro_attribute] pub fn task(args: TokenStream, item: TokenStream) -> TokenStream { - let macro_args = syn::parse_macro_input!(args as syn::AttributeArgs); - let mut task_fn = syn::parse_macro_input!(item as syn::ItemFn); + let args = syn::parse_macro_input!(args as syn::AttributeArgs); + let f = syn::parse_macro_input!(item as syn::ItemFn); - let macro_args = match TaskArgs::from_list(¯o_args) { - Ok(v) => v, - Err(e) => { - return TokenStream::from(e.write_errors()); - } - }; - - let embassy_prefix = macro_args.embassy_prefix.append("embassy"); - let embassy_path = embassy_prefix.path(); - - let pool_size: usize = macro_args.pool_size.unwrap_or(1); - - let mut fail = false; - if task_fn.sig.asyncness.is_none() { - task_fn - .sig - .span() - .unwrap() - .error("task functions must be async") - .emit(); - fail = true; - } - if !task_fn.sig.generics.params.is_empty() { - task_fn - .sig - .span() - .unwrap() - .error("task functions must not be generic") - .emit(); - fail = true; - } - if pool_size < 1 { - return parse::Error::new(Span::call_site(), "pool_size must be 1 or greater") - .to_compile_error() - .into(); - } - - let mut arg_names: syn::punctuated::Punctuated = - syn::punctuated::Punctuated::new(); - let mut args = task_fn.sig.inputs.clone(); - - for arg in args.iter_mut() { - match arg { - syn::FnArg::Receiver(_) => { - arg.span() - .unwrap() - .error("task functions must not have receiver arguments") - .emit(); - fail = true; - } - syn::FnArg::Typed(t) => match t.pat.as_mut() { - syn::Pat::Ident(i) => { - arg_names.push(i.ident.clone()); - i.mutability = None; - } - _ => { - arg.span() - .unwrap() - .error("pattern matching in task arguments is not yet supporteds") - .emit(); - fail = true; - } - }, - } - } - - if fail { - return TokenStream::new(); - } - - let name = task_fn.sig.ident.clone(); - - let visibility = &task_fn.vis; - task_fn.sig.ident = format_ident!("task"); - let impl_ty = if macro_args.send { - quote!(impl ::core::future::Future + Send + 'static) - } else { - quote!(impl ::core::future::Future + 'static) - }; - - let attrs = &task_fn.attrs; - - let result = quote! { - #(#attrs)* - #visibility fn #name(#args) -> #embassy_path::executor::SpawnToken<#impl_ty> { - use #embassy_path::executor::raw::TaskStorage; - #task_fn - type F = #impl_ty; - #[allow(clippy::declare_interior_mutable_const)] - const NEW_TASK: TaskStorage = TaskStorage::new(); - static POOL: [TaskStorage; #pool_size] = [NEW_TASK; #pool_size]; - unsafe { TaskStorage::spawn_pool(&POOL, move || task(#arg_names)) } - } - }; - result.into() + task::run(args, f).unwrap_or_else(|x| x).into() } #[proc_macro_attribute] -pub fn interrupt(args: TokenStream, input: TokenStream) -> TokenStream { - let mut f: ItemFn = syn::parse(input).expect("`#[interrupt]` must be applied to a function"); +pub fn main(args: TokenStream, item: TokenStream) -> TokenStream { + let args = syn::parse_macro_input!(args as syn::AttributeArgs); + let f = syn::parse_macro_input!(item as syn::ItemFn); + main::run(args, f).unwrap_or_else(|x| x).into() +} - if !args.is_empty() { - return parse::Error::new(Span::call_site(), "This attribute accepts no arguments") - .to_compile_error() - .into(); - } - - let fspan = f.span(); - let ident = f.sig.ident.clone(); - let ident_s = ident.to_string(); - - // XXX should we blacklist other attributes? - - let valid_signature = f.sig.constness.is_none() - && f.vis == Visibility::Inherited - && f.sig.abi.is_none() - && f.sig.inputs.is_empty() - && f.sig.generics.params.is_empty() - && f.sig.generics.where_clause.is_none() - && f.sig.variadic.is_none() - && match f.sig.output { - ReturnType::Default => true, - ReturnType::Type(_, ref ty) => match **ty { - Type::Tuple(ref tuple) => tuple.elems.is_empty(), - Type::Never(..) => true, - _ => false, - }, - }; - - if !valid_signature { - return parse::Error::new( - fspan, - "`#[interrupt]` handlers must have signature `[unsafe] fn() [-> !]`", - ) - .to_compile_error() - .into(); - } - - f.block.stmts = iter::once( - syn::parse2(quote! {{ - // Check that this interrupt actually exists - let __irq_exists_check: interrupt::#ident; - }}) - .unwrap(), - ) - .chain(f.block.stmts) - .collect(); - - quote!( - #[doc(hidden)] - #[export_name = #ident_s] - #[allow(non_snake_case)] - #f - ) - .into() +#[proc_macro_attribute] +pub fn interrupt(args: TokenStream, item: TokenStream) -> TokenStream { + let args = syn::parse_macro_input!(args as syn::AttributeArgs); + let f = syn::parse_macro_input!(item as syn::ItemFn); + interrupt::run(args, f).unwrap_or_else(|x| x).into() } #[proc_macro] pub fn interrupt_declare(item: TokenStream) -> TokenStream { let name = syn::parse_macro_input!(item as syn::Ident); - let name = format_ident!("{}", name); - let name_interrupt = format_ident!("{}", name); - let name_handler = format!("__EMBASSY_{}_HANDLER", name); - - let result = quote! { - #[allow(non_camel_case_types)] - pub struct #name_interrupt(()); - unsafe impl ::embassy::interrupt::Interrupt for #name_interrupt { - type Priority = crate::interrupt::Priority; - fn number(&self) -> u16 { - use cortex_m::interrupt::InterruptNumber; - let irq = InterruptEnum::#name; - irq.number() as u16 - } - unsafe fn steal() -> Self { - Self(()) - } - unsafe fn __handler(&self) -> &'static ::embassy::interrupt::Handler { - #[export_name = #name_handler] - static HANDLER: ::embassy::interrupt::Handler = ::embassy::interrupt::Handler::new(); - &HANDLER - } - } - - unsafe impl ::embassy::util::Unborrow for #name_interrupt { - type Target = #name_interrupt; - unsafe fn unborrow(self) -> #name_interrupt { - self - } - } - }; - result.into() + interrupt_declare::run(name).unwrap_or_else(|x| x).into() } + /// # interrupt_take procedural macro /// /// core::panic! is used as a default way to panic in this macro as there is no sensible way of enabling/disabling defmt for macro generation. @@ -231,312 +42,5 @@ pub fn interrupt_declare(item: TokenStream) -> TokenStream { #[proc_macro] pub fn interrupt_take(item: TokenStream) -> TokenStream { let name = syn::parse_macro_input!(item as syn::Ident); - let name = format!("{}", name); - let name_interrupt = format_ident!("{}", name); - let name_handler = format!("__EMBASSY_{}_HANDLER", name); - - let result = quote! { - { - #[allow(non_snake_case)] - #[export_name = #name] - pub unsafe extern "C" fn trampoline() { - extern "C" { - #[link_name = #name_handler] - static HANDLER: ::embassy::interrupt::Handler; - } - - let func = HANDLER.func.load(::embassy::export::atomic::Ordering::Relaxed); - let ctx = HANDLER.ctx.load(::embassy::export::atomic::Ordering::Relaxed); - let func: fn(*mut ()) = ::core::mem::transmute(func); - func(ctx) - } - - static TAKEN: ::embassy::export::atomic::AtomicBool = ::embassy::export::atomic::AtomicBool::new(false); - - if TAKEN.compare_exchange(false, true, ::embassy::export::atomic::Ordering::AcqRel, ::embassy::export::atomic::Ordering::Acquire).is_err() { - core::panic!("IRQ Already taken"); - } - - let irq: interrupt::#name_interrupt = unsafe { ::core::mem::transmute(()) }; - irq - } - }; - result.into() -} - -#[cfg(feature = "stm32")] -#[path = "chip/stm32.rs"] -mod chip; - -#[cfg(feature = "nrf")] -#[path = "chip/nrf.rs"] -mod chip; - -#[cfg(feature = "rp")] -#[path = "chip/rp.rs"] -mod chip; - -#[cfg(any( - feature = "nrf", - feature = "rp", - feature = "stm32", - feature = "wasm", - feature = "std" -))] -#[derive(Debug, FromMeta)] -struct MainArgs { - #[darling(default)] - embassy_prefix: ModulePrefix, - - #[allow(unused)] - #[darling(default)] - config: Option, -} - -#[cfg(any(feature = "nrf", feature = "rp", feature = "stm32"))] -#[proc_macro_attribute] -pub fn main(args: TokenStream, item: TokenStream) -> TokenStream { - let macro_args = syn::parse_macro_input!(args as syn::AttributeArgs); - let task_fn = syn::parse_macro_input!(item as syn::ItemFn); - - let macro_args = match MainArgs::from_list(¯o_args) { - Ok(v) => v, - Err(e) => { - return TokenStream::from(e.write_errors()); - } - }; - - let mut fail = false; - if task_fn.sig.asyncness.is_none() { - task_fn - .sig - .span() - .unwrap() - .error("task functions must be async") - .emit(); - fail = true; - } - if !task_fn.sig.generics.params.is_empty() { - task_fn - .sig - .span() - .unwrap() - .error("main function must not be generic") - .emit(); - fail = true; - } - - let args = task_fn.sig.inputs.clone(); - - if args.len() != 2 { - task_fn - .sig - .span() - .unwrap() - .error("main function must have 2 arguments") - .emit(); - fail = true; - } - - if fail { - return TokenStream::new(); - } - - let embassy_prefix = macro_args.embassy_prefix; - let embassy_prefix_lit = embassy_prefix.literal(); - let embassy_path = embassy_prefix.append("embassy").path(); - let task_fn_body = task_fn.block; - - let config = macro_args - .config - .map(|s| s.parse::().unwrap()) - .unwrap_or_else(|| { - syn::Expr::Verbatim(quote! { - Default::default() - }) - }); - - let chip_setup = chip::generate(&embassy_prefix, config); - - let result = quote! { - #[#embassy_path::task(embassy_prefix = #embassy_prefix_lit)] - async fn __embassy_main(#args) { - #task_fn_body - } - - #[cortex_m_rt::entry] - fn main() -> ! { - unsafe fn make_static(t: &mut T) -> &'static mut T { - ::core::mem::transmute(t) - } - - #chip_setup - - let mut executor = #embassy_path::executor::Executor::new(); - let executor = unsafe { make_static(&mut executor) }; - - executor.run(|spawner| { - spawner.must_spawn(__embassy_main(spawner, p)); - }) - } - }; - result.into() -} - -#[cfg(feature = "std")] -#[proc_macro_attribute] -pub fn main(args: TokenStream, item: TokenStream) -> TokenStream { - let macro_args = syn::parse_macro_input!(args as syn::AttributeArgs); - let task_fn = syn::parse_macro_input!(item as syn::ItemFn); - - let macro_args = match MainArgs::from_list(¯o_args) { - Ok(v) => v, - Err(e) => { - return TokenStream::from(e.write_errors()); - } - }; - - let embassy_path = macro_args.embassy_prefix.append("embassy"); - - let mut fail = false; - if task_fn.sig.asyncness.is_none() { - task_fn - .sig - .span() - .unwrap() - .error("task functions must be async") - .emit(); - fail = true; - } - if !task_fn.sig.generics.params.is_empty() { - task_fn - .sig - .span() - .unwrap() - .error("main function must not be generic") - .emit(); - fail = true; - } - - let args = task_fn.sig.inputs.clone(); - - if args.len() != 1 { - task_fn - .sig - .span() - .unwrap() - .error("main function must have one argument") - .emit(); - fail = true; - } - - if fail { - return TokenStream::new(); - } - - let task_fn_body = task_fn.block.clone(); - - let embassy_path = embassy_path.path(); - let embassy_prefix_lit = macro_args.embassy_prefix.literal(); - - let result = quote! { - #[#embassy_path::task(embassy_prefix = #embassy_prefix_lit)] - async fn __embassy_main(#args) { - #task_fn_body - } - - fn main() -> ! { - unsafe fn make_static(t: &mut T) -> &'static mut T { - ::core::mem::transmute(t) - } - - let mut executor = #embassy_path::executor::Executor::new(); - let executor = unsafe { make_static(&mut executor) }; - - executor.run(|spawner| { - spawner.spawn(__embassy_main(spawner)).unwrap(); - }) - - } - }; - result.into() -} - -#[cfg(feature = "wasm")] -#[proc_macro_attribute] -pub fn main(args: TokenStream, item: TokenStream) -> TokenStream { - let macro_args = syn::parse_macro_input!(args as syn::AttributeArgs); - let task_fn = syn::parse_macro_input!(item as syn::ItemFn); - - let macro_args = match MainArgs::from_list(¯o_args) { - Ok(v) => v, - Err(e) => { - return TokenStream::from(e.write_errors()); - } - }; - - let embassy_path = macro_args.embassy_prefix.append("embassy"); - - let mut fail = false; - if task_fn.sig.asyncness.is_none() { - task_fn - .sig - .span() - .unwrap() - .error("task functions must be async") - .emit(); - fail = true; - } - if !task_fn.sig.generics.params.is_empty() { - task_fn - .sig - .span() - .unwrap() - .error("main function must not be generic") - .emit(); - fail = true; - } - - let args = task_fn.sig.inputs.clone(); - - if args.len() != 1 { - task_fn - .sig - .span() - .unwrap() - .error("main function must have one argument") - .emit(); - fail = true; - } - - if fail { - return TokenStream::new(); - } - - let task_fn_body = task_fn.block.clone(); - - let embassy_path = embassy_path.path(); - let embassy_prefix_lit = macro_args.embassy_prefix.literal(); - - let result = quote! { - #[#embassy_path::task(embassy_prefix = #embassy_prefix_lit)] - async fn __embassy_main(#args) { - #task_fn_body - } - - use wasm_bindgen::prelude::*; - - #[wasm_bindgen(start)] - pub fn main() -> Result<(), JsValue> { - static EXECUTOR: #embassy_path::util::Forever<#embassy_path::executor::Executor> = #embassy_path::util::Forever::new(); - let executor = EXECUTOR.put(#embassy_path::executor::Executor::new()); - - executor.start(|spawner| { - spawner.spawn(__embassy_main(spawner)).unwrap(); - }); - - Ok(()) - } - }; - result.into() + interrupt_take::run(name).unwrap_or_else(|x| x).into() } diff --git a/embassy-macros/src/macros/interrupt.rs b/embassy-macros/src/macros/interrupt.rs new file mode 100644 index 00000000..32cc0e01 --- /dev/null +++ b/embassy-macros/src/macros/interrupt.rs @@ -0,0 +1,66 @@ +use darling::FromMeta; +use proc_macro2::TokenStream; +use quote::quote; +use std::iter; +use syn::ReturnType; +use syn::{Type, Visibility}; + +use crate::util::ctxt::Ctxt; + +#[derive(Debug, FromMeta)] +struct Args {} + +pub fn run(args: syn::AttributeArgs, mut f: syn::ItemFn) -> Result { + let _args = Args::from_list(&args).map_err(|e| e.write_errors())?; + + let ident = f.sig.ident.clone(); + let ident_s = ident.to_string(); + + // XXX should we blacklist other attributes? + + let valid_signature = f.sig.constness.is_none() + && f.vis == Visibility::Inherited + && f.sig.abi.is_none() + && f.sig.inputs.is_empty() + && f.sig.generics.params.is_empty() + && f.sig.generics.where_clause.is_none() + && f.sig.variadic.is_none() + && match f.sig.output { + ReturnType::Default => true, + ReturnType::Type(_, ref ty) => match **ty { + Type::Tuple(ref tuple) => tuple.elems.is_empty(), + Type::Never(..) => true, + _ => false, + }, + }; + + let ctxt = Ctxt::new(); + + if !valid_signature { + ctxt.error_spanned_by( + &f.sig, + "`#[interrupt]` handlers must have signature `[unsafe] fn() [-> !]`", + ); + } + + ctxt.check()?; + + f.block.stmts = iter::once( + syn::parse2(quote! {{ + // Check that this interrupt actually exists + let __irq_exists_check: interrupt::#ident; + }}) + .unwrap(), + ) + .chain(f.block.stmts) + .collect(); + + let result = quote!( + #[doc(hidden)] + #[export_name = #ident_s] + #[allow(non_snake_case)] + #f + ); + + Ok(result) +} diff --git a/embassy-macros/src/macros/interrupt_declare.rs b/embassy-macros/src/macros/interrupt_declare.rs new file mode 100644 index 00000000..0059936d --- /dev/null +++ b/embassy-macros/src/macros/interrupt_declare.rs @@ -0,0 +1,37 @@ +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; + +pub fn run(name: syn::Ident) -> Result { + let name = format_ident!("{}", name); + let name_interrupt = format_ident!("{}", name); + let name_handler = format!("__EMBASSY_{}_HANDLER", name); + + let result = quote! { + #[allow(non_camel_case_types)] + pub struct #name_interrupt(()); + unsafe impl ::embassy::interrupt::Interrupt for #name_interrupt { + type Priority = crate::interrupt::Priority; + fn number(&self) -> u16 { + use cortex_m::interrupt::InterruptNumber; + let irq = InterruptEnum::#name; + irq.number() as u16 + } + unsafe fn steal() -> Self { + Self(()) + } + unsafe fn __handler(&self) -> &'static ::embassy::interrupt::Handler { + #[export_name = #name_handler] + static HANDLER: ::embassy::interrupt::Handler = ::embassy::interrupt::Handler::new(); + &HANDLER + } + } + + unsafe impl ::embassy::util::Unborrow for #name_interrupt { + type Target = #name_interrupt; + unsafe fn unborrow(self) -> #name_interrupt { + self + } + } + }; + Ok(result) +} diff --git a/embassy-macros/src/macros/interrupt_take.rs b/embassy-macros/src/macros/interrupt_take.rs new file mode 100644 index 00000000..230b9c74 --- /dev/null +++ b/embassy-macros/src/macros/interrupt_take.rs @@ -0,0 +1,36 @@ +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; + +pub fn run(name: syn::Ident) -> Result { + let name = format!("{}", name); + let name_interrupt = format_ident!("{}", name); + let name_handler = format!("__EMBASSY_{}_HANDLER", name); + + let result = quote! { + { + #[allow(non_snake_case)] + #[export_name = #name] + pub unsafe extern "C" fn trampoline() { + extern "C" { + #[link_name = #name_handler] + static HANDLER: ::embassy::interrupt::Handler; + } + + let func = HANDLER.func.load(::embassy::export::atomic::Ordering::Relaxed); + let ctx = HANDLER.ctx.load(::embassy::export::atomic::Ordering::Relaxed); + let func: fn(*mut ()) = ::core::mem::transmute(func); + func(ctx) + } + + static TAKEN: ::embassy::export::atomic::AtomicBool = ::embassy::export::atomic::AtomicBool::new(false); + + if TAKEN.compare_exchange(false, true, ::embassy::export::atomic::Ordering::AcqRel, ::embassy::export::atomic::Ordering::Acquire).is_err() { + core::panic!("IRQ Already taken"); + } + + let irq: interrupt::#name_interrupt = unsafe { ::core::mem::transmute(()) }; + irq + } + }; + Ok(result) +} diff --git a/embassy-macros/src/macros/main.rs b/embassy-macros/src/macros/main.rs new file mode 100644 index 00000000..01e30292 --- /dev/null +++ b/embassy-macros/src/macros/main.rs @@ -0,0 +1,135 @@ +use darling::FromMeta; +use proc_macro2::TokenStream; +use quote::quote; + +use crate::util::ctxt::Ctxt; +use crate::util::path::ModulePrefix; + +#[cfg(feature = "stm32")] +const HAL: Option<&str> = Some("embassy_stm32"); +#[cfg(feature = "nrf")] +const HAL: Option<&str> = Some("embassy_nrf"); +#[cfg(feature = "rp")] +const HAL: Option<&str> = Some("embassy_rp"); +#[cfg(not(any(feature = "stm32", feature = "nrf", feature = "rp")))] +const HAL: Option<&str> = None; + +#[derive(Debug, FromMeta)] +struct Args { + #[darling(default)] + embassy_prefix: ModulePrefix, + + #[allow(unused)] + #[darling(default)] + config: Option, +} + +pub fn run(args: syn::AttributeArgs, f: syn::ItemFn) -> Result { + let args = Args::from_list(&args).map_err(|e| e.write_errors())?; + + let fargs = f.sig.inputs.clone(); + + let ctxt = Ctxt::new(); + + if f.sig.asyncness.is_none() { + ctxt.error_spanned_by(&f.sig, "task functions must be async"); + } + if !f.sig.generics.params.is_empty() { + ctxt.error_spanned_by(&f.sig, "task functions must not be generic"); + } + + if HAL.is_some() && fargs.len() != 2 { + ctxt.error_spanned_by(&f.sig, "main function must have 2 arguments"); + } + if HAL.is_none() && fargs.len() != 1 { + ctxt.error_spanned_by(&f.sig, "main function must have 2 arguments"); + } + + ctxt.check()?; + + let embassy_prefix = args.embassy_prefix; + let embassy_prefix_lit = embassy_prefix.literal(); + let embassy_path = embassy_prefix.append("embassy").path(); + let f_body = f.block; + + #[cfg(feature = "wasm")] + let main = quote! { + #[wasm_bindgen::prelude::wasm_bindgen(start)] + pub fn main() -> Result<(), wasm_bindgen::JsValue> { + static EXECUTOR: #embassy_path::util::Forever<#embassy_path::executor::Executor> = #embassy_path::util::Forever::new(); + let executor = EXECUTOR.put(#embassy_path::executor::Executor::new()); + + executor.start(|spawner| { + spawner.spawn(__embassy_main(spawner)).unwrap(); + }); + + Ok(()) + } + }; + + #[cfg(all(feature = "std", not(feature = "wasm")))] + let main = quote! { + fn main() -> ! { + let mut executor = #embassy_path::executor::Executor::new(); + let executor = unsafe { __make_static(&mut executor) }; + + executor.run(|spawner| { + spawner.must_spawn(__embassy_main(spawner)); + }) + } + }; + + #[cfg(all(not(feature = "std"), not(feature = "wasm")))] + let main = { + let config = args + .config + .map(|s| s.parse::().unwrap()) + .unwrap_or_else(|| { + syn::Expr::Verbatim(quote! { + Default::default() + }) + }); + + let (hal_setup, peris_arg) = match HAL { + Some(hal) => { + let embassy_hal_path = embassy_prefix.append(hal).path(); + ( + quote!( + let p = #embassy_hal_path::init(#config); + ), + quote!(p), + ) + } + None => (quote!(), quote!()), + }; + + quote! { + #[cortex_m_rt::entry] + fn main() -> ! { + #hal_setup + + let mut executor = #embassy_path::executor::Executor::new(); + let executor = unsafe { __make_static(&mut executor) }; + + executor.run(|spawner| { + spawner.must_spawn(__embassy_main(spawner, #peris_arg)); + }) + } + } + }; + + let result = quote! { + #[#embassy_path::task(embassy_prefix = #embassy_prefix_lit)] + async fn __embassy_main(#fargs) { + #f_body + } + + unsafe fn __make_static(t: &mut T) -> &'static mut T { + ::core::mem::transmute(t) + } + + #main + }; + + Ok(result) +} diff --git a/embassy-macros/src/macros/mod.rs b/embassy-macros/src/macros/mod.rs new file mode 100644 index 00000000..4350f229 --- /dev/null +++ b/embassy-macros/src/macros/mod.rs @@ -0,0 +1,5 @@ +pub mod interrupt; +pub mod interrupt_declare; +pub mod interrupt_take; +pub mod main; +pub mod task; diff --git a/embassy-macros/src/macros/task.rs b/embassy-macros/src/macros/task.rs new file mode 100644 index 00000000..f0c78c59 --- /dev/null +++ b/embassy-macros/src/macros/task.rs @@ -0,0 +1,90 @@ +use darling::FromMeta; +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; + +use crate::util::ctxt::Ctxt; +use crate::util::path::ModulePrefix; + +#[derive(Debug, FromMeta)] +struct Args { + #[darling(default)] + pool_size: Option, + #[darling(default)] + send: bool, + #[darling(default)] + embassy_prefix: ModulePrefix, +} + +pub fn run(args: syn::AttributeArgs, mut f: syn::ItemFn) -> Result { + let args = Args::from_list(&args).map_err(|e| e.write_errors())?; + + let embassy_prefix = args.embassy_prefix.append("embassy"); + let embassy_path = embassy_prefix.path(); + + let pool_size: usize = args.pool_size.unwrap_or(1); + + let ctxt = Ctxt::new(); + + if f.sig.asyncness.is_none() { + ctxt.error_spanned_by(&f.sig, "task functions must be async"); + } + if !f.sig.generics.params.is_empty() { + ctxt.error_spanned_by(&f.sig, "task functions must not be generic"); + } + if pool_size < 1 { + ctxt.error_spanned_by(&f.sig, "pool_size must be 1 or greater"); + } + + let mut arg_names: syn::punctuated::Punctuated = + syn::punctuated::Punctuated::new(); + let mut fargs = f.sig.inputs.clone(); + + for arg in fargs.iter_mut() { + match arg { + syn::FnArg::Receiver(_) => { + ctxt.error_spanned_by(arg, "task functions must not have receiver arguments"); + } + syn::FnArg::Typed(t) => match t.pat.as_mut() { + syn::Pat::Ident(i) => { + arg_names.push(i.ident.clone()); + i.mutability = None; + } + _ => { + ctxt.error_spanned_by( + arg, + "pattern matching in task arguments is not yet supporteds", + ); + } + }, + } + } + + ctxt.check()?; + + let name = f.sig.ident.clone(); + + let visibility = &f.vis; + f.sig.ident = format_ident!("task"); + let impl_ty = if args.send { + quote!(impl ::core::future::Future + Send + 'static) + } else { + quote!(impl ::core::future::Future + 'static) + }; + + let attrs = &f.attrs; + + let result = quote! { + #(#attrs)* + #visibility fn #name(#fargs) -> #embassy_path::executor::SpawnToken<#impl_ty> { + use #embassy_path::executor::raw::TaskStorage; + #f + type F = #impl_ty; + #[allow(clippy::declare_interior_mutable_const)] + const NEW_TASK: TaskStorage = TaskStorage::new(); + static POOL: [TaskStorage; #pool_size] = [NEW_TASK; #pool_size]; + unsafe { TaskStorage::spawn_pool(&POOL, move || task(#arg_names)) } + } + }; + + Ok(result) +} diff --git a/embassy-macros/src/util/ctxt.rs b/embassy-macros/src/util/ctxt.rs new file mode 100644 index 00000000..d668ae78 --- /dev/null +++ b/embassy-macros/src/util/ctxt.rs @@ -0,0 +1,72 @@ +// nifty utility borrowed from serde :) +// https://github.com/serde-rs/serde/blob/master/serde_derive/src/internals/ctxt.rs + +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use std::cell::RefCell; +use std::fmt::Display; +use std::thread; +use syn; + +/// A type to collect errors together and format them. +/// +/// Dropping this object will cause a panic. It must be consumed using `check`. +/// +/// References can be shared since this type uses run-time exclusive mut checking. +#[derive(Default)] +pub struct Ctxt { + // The contents will be set to `None` during checking. This is so that checking can be + // enforced. + errors: RefCell>>, +} + +impl Ctxt { + /// Create a new context object. + /// + /// This object contains no errors, but will still trigger a panic if it is not `check`ed. + pub fn new() -> Self { + Ctxt { + errors: RefCell::new(Some(Vec::new())), + } + } + + /// Add an error to the context object with a tokenenizable object. + /// + /// The object is used for spanning in error messages. + pub fn error_spanned_by(&self, obj: A, msg: T) { + self.errors + .borrow_mut() + .as_mut() + .unwrap() + // Curb monomorphization from generating too many identical methods. + .push(syn::Error::new_spanned(obj.into_token_stream(), msg)); + } + + /// Add one of Syn's parse errors. + #[allow(unused)] + pub fn syn_error(&self, err: syn::Error) { + self.errors.borrow_mut().as_mut().unwrap().push(err); + } + + /// Consume this object, producing a formatted error string if there are errors. + pub fn check(self) -> Result<(), TokenStream> { + let errors = self.errors.borrow_mut().take().unwrap(); + match errors.len() { + 0 => Ok(()), + _ => Err(to_compile_errors(errors)), + } + } +} + +fn to_compile_errors(errors: Vec) -> proc_macro2::TokenStream { + let compile_errors = errors.iter().map(syn::Error::to_compile_error); + quote!(#(#compile_errors)*) +} + +impl Drop for Ctxt { + fn drop(&mut self) { + if !thread::panicking() && self.errors.borrow().is_some() { + panic!("forgot to check for errors"); + } + } +} diff --git a/embassy-macros/src/util/mod.rs b/embassy-macros/src/util/mod.rs new file mode 100644 index 00000000..c2f2dfd6 --- /dev/null +++ b/embassy-macros/src/util/mod.rs @@ -0,0 +1,2 @@ +pub mod ctxt; +pub mod path; diff --git a/embassy-macros/src/path.rs b/embassy-macros/src/util/path.rs similarity index 100% rename from embassy-macros/src/path.rs rename to embassy-macros/src/util/path.rs