From 9e6e09a8d747ec90aae215df8471dfe349993487 Mon Sep 17 00:00:00 2001 From: Dummyc0m Date: Sun, 6 Oct 2024 23:23:33 -0700 Subject: executor/spin: introduce an architecture agnostic executor Spin polls the raw executor and never sleeps. It is useful for disabling any power features associated with wfi/wfe-like instructions. When implementing support for the CH32V30x MCU, the wfi instruction had issues interacting with the USB OTG peripheral and appeared to be non-spec-compliant. 1. When sending a USB Data-in packet, the USB peripheral appears to be unable to read the system main memory while in WFI. This manifests in the USB peripheral sending all or partially zeroed DATA packets. Disabling WFI works around this issue. 2. The WFI instruction does not wake up the processor when MIE is disabled. The MCU provides a WFITOWFE bit to emulate the WFE instruction on arm, which, when enabled, ignores the MIE and allows the processor to wake up. This works around the non-compliant WFI implementation. Co-authored-by: Codetector Co-authored-by: Dummyc0m --- embassy-executor-macros/src/lib.rs | 29 +++++++++++++++++++++++++++++ embassy-executor-macros/src/macros/main.rs | 29 ++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 1 deletion(-) (limited to 'embassy-executor-macros/src') diff --git a/embassy-executor-macros/src/lib.rs b/embassy-executor-macros/src/lib.rs index 5461fe04c..61d388b9e 100644 --- a/embassy-executor-macros/src/lib.rs +++ b/embassy-executor-macros/src/lib.rs @@ -94,6 +94,35 @@ pub fn main_cortex_m(args: TokenStream, item: TokenStream) -> TokenStream { main::run(&args.meta, f, main::cortex_m()).unwrap_or_else(|x| x).into() } +/// Creates a new `executor` instance and declares an architecture agnostic application entry point spawning +/// the corresponding function body as an async task. +/// +/// The following restrictions apply: +/// +/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks. +/// * The function must be declared `async`. +/// * The function must not use generics. +/// * Only a single `main` task may be declared. +/// +/// A user-defined entry macro must provided via the `entry` argument +/// +/// ## Examples +/// Spawning a task: +/// ``` rust +/// #[embassy_executor::main(entry = "qingke_rt::entry")] +/// async fn main(_s: embassy_executor::Spawner) { +/// // Function body +/// } +/// ``` +#[proc_macro_attribute] +pub fn main_spin(args: TokenStream, item: TokenStream) -> TokenStream { + let args = syn::parse_macro_input!(args as Args); + let f = syn::parse_macro_input!(item as syn::ItemFn); + main::run(&args.meta, f, main::spin(&args.meta)) + .unwrap_or_else(|x| x) + .into() +} + /// Creates a new `executor` instance and declares an application entry point for RISC-V spawning the corresponding function body as an async task. /// /// The following restrictions apply: diff --git a/embassy-executor-macros/src/macros/main.rs b/embassy-executor-macros/src/macros/main.rs index 26dfa2397..66a3965d0 100644 --- a/embassy-executor-macros/src/macros/main.rs +++ b/embassy-executor-macros/src/macros/main.rs @@ -1,5 +1,5 @@ use darling::export::NestedMeta; -use darling::FromMeta; +use darling::{Error, FromMeta}; use proc_macro2::TokenStream; use quote::quote; use syn::{Expr, ReturnType, Type}; @@ -50,6 +50,33 @@ pub fn riscv(args: &[NestedMeta]) -> TokenStream { } } +pub fn spin(args: &[NestedMeta]) -> TokenStream { + let maybe_entry = match Args::from_list(args) { + Ok(args) => args.entry, + Err(e) => return e.write_errors(), + }; + + let entry = match maybe_entry { + Some(str) => str, + None => return Error::missing_field("entry").write_errors(), + }; + let entry = match Expr::from_string(&entry) { + Ok(expr) => expr, + Err(e) => return e.write_errors(), + }; + + quote! { + #[#entry] + fn main() -> ! { + let mut executor = ::embassy_executor::Executor::new(); + let executor = unsafe { __make_static(&mut executor) }; + executor.run(|spawner| { + spawner.must_spawn(__embassy_main(spawner)); + }) + } + } +} + pub fn cortex_m() -> TokenStream { quote! { #[cortex_m_rt::entry] -- cgit From 1f58e0efd05e5c96409db301e4d481098038aedf Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Fri, 18 Oct 2024 03:18:59 +0200 Subject: executor: fix unsoundness due to `impl Trait`, improve macro error handling. (#3425) * executor-macros: don't parse function bodies. * executor-macros: refactor for better recovery and ide-friendliness on errors. * executor-macros: disallow `impl Trait` in task arguments. Fixes #3420 * Fix example using `impl Trait` in tasks. --- embassy-executor-macros/src/lib.rs | 50 +----- embassy-executor-macros/src/macros/main.rs | 240 ++++++++++++++--------------- embassy-executor-macros/src/macros/task.rs | 151 ++++++++++++------ embassy-executor-macros/src/util.rs | 74 +++++++++ embassy-executor-macros/src/util/ctxt.rs | 72 --------- embassy-executor-macros/src/util/mod.rs | 1 - 6 files changed, 302 insertions(+), 286 deletions(-) create mode 100644 embassy-executor-macros/src/util.rs delete mode 100644 embassy-executor-macros/src/util/ctxt.rs delete mode 100644 embassy-executor-macros/src/util/mod.rs (limited to 'embassy-executor-macros/src') diff --git a/embassy-executor-macros/src/lib.rs b/embassy-executor-macros/src/lib.rs index 61d388b9e..5f2182f10 100644 --- a/embassy-executor-macros/src/lib.rs +++ b/embassy-executor-macros/src/lib.rs @@ -1,28 +1,11 @@ #![doc = include_str!("../README.md")] extern crate proc_macro; -use darling::ast::NestedMeta; use proc_macro::TokenStream; mod macros; mod util; use macros::*; -use syn::parse::{Parse, ParseBuffer}; -use syn::punctuated::Punctuated; -use syn::Token; - -struct Args { - meta: Vec, -} - -impl Parse for Args { - fn parse(input: &ParseBuffer) -> syn::Result { - let meta = Punctuated::::parse_terminated(input)?; - Ok(Args { - meta: meta.into_iter().collect(), - }) - } -} /// Declares an async task that can be run by `embassy-executor`. The optional `pool_size` parameter can be used to specify how /// many concurrent tasks can be spawned (default is 1) for the function. @@ -56,17 +39,12 @@ impl Parse for Args { /// ``` #[proc_macro_attribute] pub fn task(args: TokenStream, item: TokenStream) -> TokenStream { - let args = syn::parse_macro_input!(args as Args); - let f = syn::parse_macro_input!(item as syn::ItemFn); - - task::run(&args.meta, f).unwrap_or_else(|x| x).into() + task::run(args.into(), item.into()).into() } #[proc_macro_attribute] pub fn main_avr(args: TokenStream, item: TokenStream) -> TokenStream { - let args = syn::parse_macro_input!(args as Args); - let f = syn::parse_macro_input!(item as syn::ItemFn); - main::run(&args.meta, f, main::avr()).unwrap_or_else(|x| x).into() + main::run(args.into(), item.into(), &main::ARCH_AVR).into() } /// Creates a new `executor` instance and declares an application entry point for Cortex-M spawning the corresponding function body as an async task. @@ -89,9 +67,7 @@ pub fn main_avr(args: TokenStream, item: TokenStream) -> TokenStream { /// ``` #[proc_macro_attribute] pub fn main_cortex_m(args: TokenStream, item: TokenStream) -> TokenStream { - let args = syn::parse_macro_input!(args as Args); - let f = syn::parse_macro_input!(item as syn::ItemFn); - main::run(&args.meta, f, main::cortex_m()).unwrap_or_else(|x| x).into() + main::run(args.into(), item.into(), &main::ARCH_CORTEX_M).into() } /// Creates a new `executor` instance and declares an architecture agnostic application entry point spawning @@ -116,11 +92,7 @@ pub fn main_cortex_m(args: TokenStream, item: TokenStream) -> TokenStream { /// ``` #[proc_macro_attribute] pub fn main_spin(args: TokenStream, item: TokenStream) -> TokenStream { - let args = syn::parse_macro_input!(args as Args); - let f = syn::parse_macro_input!(item as syn::ItemFn); - main::run(&args.meta, f, main::spin(&args.meta)) - .unwrap_or_else(|x| x) - .into() + main::run(args.into(), item.into(), &main::ARCH_SPIN).into() } /// Creates a new `executor` instance and declares an application entry point for RISC-V spawning the corresponding function body as an async task. @@ -153,11 +125,7 @@ pub fn main_spin(args: TokenStream, item: TokenStream) -> TokenStream { /// ``` #[proc_macro_attribute] pub fn main_riscv(args: TokenStream, item: TokenStream) -> TokenStream { - let args = syn::parse_macro_input!(args as Args); - let f = syn::parse_macro_input!(item as syn::ItemFn); - main::run(&args.meta, f, main::riscv(&args.meta)) - .unwrap_or_else(|x| x) - .into() + main::run(args.into(), item.into(), &main::ARCH_RISCV).into() } /// Creates a new `executor` instance and declares an application entry point for STD spawning the corresponding function body as an async task. @@ -180,9 +148,7 @@ pub fn main_riscv(args: TokenStream, item: TokenStream) -> TokenStream { /// ``` #[proc_macro_attribute] pub fn main_std(args: TokenStream, item: TokenStream) -> TokenStream { - let args = syn::parse_macro_input!(args as Args); - let f = syn::parse_macro_input!(item as syn::ItemFn); - main::run(&args.meta, f, main::std()).unwrap_or_else(|x| x).into() + main::run(args.into(), item.into(), &main::ARCH_STD).into() } /// Creates a new `executor` instance and declares an application entry point for WASM spawning the corresponding function body as an async task. @@ -205,7 +171,5 @@ pub fn main_std(args: TokenStream, item: TokenStream) -> TokenStream { /// ``` #[proc_macro_attribute] pub fn main_wasm(args: TokenStream, item: TokenStream) -> TokenStream { - let args = syn::parse_macro_input!(args as Args); - let f = syn::parse_macro_input!(item as syn::ItemFn); - main::run(&args.meta, f, main::wasm()).unwrap_or_else(|x| x).into() + main::run(args.into(), item.into(), &main::ARCH_WASM).into() } diff --git a/embassy-executor-macros/src/macros/main.rs b/embassy-executor-macros/src/macros/main.rs index 66a3965d0..14b1d9de2 100644 --- a/embassy-executor-macros/src/macros/main.rs +++ b/embassy-executor-macros/src/macros/main.rs @@ -1,152 +1,107 @@ +use std::str::FromStr; + use darling::export::NestedMeta; -use darling::{Error, FromMeta}; +use darling::FromMeta; use proc_macro2::TokenStream; use quote::quote; -use syn::{Expr, ReturnType, Type}; +use syn::{ReturnType, Type}; + +use crate::util::*; + +enum Flavor { + Standard, + Wasm, +} -use crate::util::ctxt::Ctxt; +pub(crate) struct Arch { + default_entry: Option<&'static str>, + flavor: Flavor, +} -#[derive(Debug, FromMeta)] +pub static ARCH_AVR: Arch = Arch { + default_entry: Some("avr_device::entry"), + flavor: Flavor::Standard, +}; + +pub static ARCH_RISCV: Arch = Arch { + default_entry: Some("riscv_rt::entry"), + flavor: Flavor::Standard, +}; + +pub static ARCH_CORTEX_M: Arch = Arch { + default_entry: Some("cortex_m_rt::entry"), + flavor: Flavor::Standard, +}; + +pub static ARCH_SPIN: Arch = Arch { + default_entry: None, + flavor: Flavor::Standard, +}; + +pub static ARCH_STD: Arch = Arch { + default_entry: None, + flavor: Flavor::Standard, +}; + +pub static ARCH_WASM: Arch = Arch { + default_entry: Some("wasm_bindgen::prelude::wasm_bindgen(start)"), + flavor: Flavor::Wasm, +}; + +#[derive(Debug, FromMeta, Default)] struct Args { #[darling(default)] entry: Option, } -pub fn avr() -> TokenStream { - quote! { - #[avr_device::entry] - fn main() -> ! { - let mut executor = ::embassy_executor::Executor::new(); - let executor = unsafe { __make_static(&mut executor) }; +pub fn run(args: TokenStream, item: TokenStream, arch: &Arch) -> TokenStream { + let mut errors = TokenStream::new(); - executor.run(|spawner| { - spawner.must_spawn(__embassy_main(spawner)); - }) - } - } -} - -pub fn riscv(args: &[NestedMeta]) -> TokenStream { - let maybe_entry = match Args::from_list(args) { - Ok(args) => args.entry, - Err(e) => return e.write_errors(), + // If any of the steps for this macro fail, we still want to expand to an item that is as close + // to the expected output as possible. This helps out IDEs such that completions and other + // related features keep working. + let f: ItemFn = match syn::parse2(item.clone()) { + Ok(x) => x, + Err(e) => return token_stream_with_error(item, e), }; - let entry = maybe_entry.unwrap_or("riscv_rt::entry".into()); - let entry = match Expr::from_string(&entry) { - Ok(expr) => expr, - Err(e) => return e.write_errors(), + let args = match NestedMeta::parse_meta_list(args) { + Ok(x) => x, + Err(e) => return token_stream_with_error(item, e), }; - quote! { - #[#entry] - fn main() -> ! { - let mut executor = ::embassy_executor::Executor::new(); - let executor = unsafe { __make_static(&mut executor) }; - executor.run(|spawner| { - spawner.must_spawn(__embassy_main(spawner)); - }) + let args = match Args::from_list(&args) { + Ok(x) => x, + Err(e) => { + errors.extend(e.write_errors()); + Args::default() } - } -} - -pub fn spin(args: &[NestedMeta]) -> TokenStream { - let maybe_entry = match Args::from_list(args) { - Ok(args) => args.entry, - Err(e) => return e.write_errors(), - }; - - let entry = match maybe_entry { - Some(str) => str, - None => return Error::missing_field("entry").write_errors(), }; - let entry = match Expr::from_string(&entry) { - Ok(expr) => expr, - Err(e) => return e.write_errors(), - }; - - quote! { - #[#entry] - fn main() -> ! { - let mut executor = ::embassy_executor::Executor::new(); - let executor = unsafe { __make_static(&mut executor) }; - executor.run(|spawner| { - spawner.must_spawn(__embassy_main(spawner)); - }) - } - } -} - -pub fn cortex_m() -> TokenStream { - quote! { - #[cortex_m_rt::entry] - fn main() -> ! { - let mut executor = ::embassy_executor::Executor::new(); - let executor = unsafe { __make_static(&mut executor) }; - executor.run(|spawner| { - spawner.must_spawn(__embassy_main(spawner)); - }) - } - } -} - -pub fn wasm() -> TokenStream { - quote! { - #[wasm_bindgen::prelude::wasm_bindgen(start)] - pub fn main() -> Result<(), wasm_bindgen::JsValue> { - let executor = ::std::boxed::Box::leak(::std::boxed::Box::new(::embassy_executor::Executor::new())); - - executor.start(|spawner| { - spawner.must_spawn(__embassy_main(spawner)); - }); - - Ok(()) - } - } -} - -pub fn std() -> TokenStream { - quote! { - fn main() -> ! { - let mut executor = ::embassy_executor::Executor::new(); - let executor = unsafe { __make_static(&mut executor) }; - - executor.run(|spawner| { - spawner.must_spawn(__embassy_main(spawner)); - }) - } - } -} - -pub fn run(args: &[NestedMeta], f: syn::ItemFn, main: TokenStream) -> Result { - #[allow(unused_variables)] - 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, "main function must be async"); + error(&mut errors, &f.sig, "main function must be async"); } if !f.sig.generics.params.is_empty() { - ctxt.error_spanned_by(&f.sig, "main function must not be generic"); + error(&mut errors, &f.sig, "main function must not be generic"); } if !f.sig.generics.where_clause.is_none() { - ctxt.error_spanned_by(&f.sig, "main function must not have `where` clauses"); + error(&mut errors, &f.sig, "main function must not have `where` clauses"); } if !f.sig.abi.is_none() { - ctxt.error_spanned_by(&f.sig, "main function must not have an ABI qualifier"); + error(&mut errors, &f.sig, "main function must not have an ABI qualifier"); } if !f.sig.variadic.is_none() { - ctxt.error_spanned_by(&f.sig, "main function must not be variadic"); + error(&mut errors, &f.sig, "main function must not be variadic"); } match &f.sig.output { ReturnType::Default => {} ReturnType::Type(_, ty) => match &**ty { Type::Tuple(tuple) if tuple.elems.is_empty() => {} Type::Never(_) => {} - _ => ctxt.error_spanned_by( + _ => error( + &mut errors, &f.sig, "main function must either not return a value, return `()` or return `!`", ), @@ -154,26 +109,69 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn, main: TokenStream) -> Result TokenStream::new(), + Some(x) => match TokenStream::from_str(x) { + Ok(x) => quote!(#[#x]), + Err(e) => { + error(&mut errors, &f.sig, e); + TokenStream::new() + } + }, + }; - let f_body = f.block; + let f_body = f.body; let out = &f.sig.output; + let (main_ret, mut main_body) = match arch.flavor { + Flavor::Standard => ( + quote!(!), + quote! { + unsafe fn __make_static(t: &mut T) -> &'static mut T { + ::core::mem::transmute(t) + } + + let mut executor = ::embassy_executor::Executor::new(); + let executor = unsafe { __make_static(&mut executor) }; + executor.run(|spawner| { + spawner.must_spawn(__embassy_main(spawner)); + }) + }, + ), + Flavor::Wasm => ( + quote!(Result<(), wasm_bindgen::JsValue>), + quote! { + let executor = ::std::boxed::Box::leak(::std::boxed::Box::new(::embassy_executor::Executor::new())); + + executor.start(|spawner| { + spawner.must_spawn(__embassy_main(spawner)); + }); + + Ok(()) + }, + ), + }; + + if !errors.is_empty() { + main_body = quote! {loop{}}; + } + let result = quote! { #[::embassy_executor::task()] async fn __embassy_main(#fargs) #out { #f_body } - unsafe fn __make_static(t: &mut T) -> &'static mut T { - ::core::mem::transmute(t) + #entry + fn main() -> #main_ret { + #main_body } - #main + #errors }; - Ok(result) + result } diff --git a/embassy-executor-macros/src/macros/task.rs b/embassy-executor-macros/src/macros/task.rs index 96c6267b2..0404dba64 100644 --- a/embassy-executor-macros/src/macros/task.rs +++ b/embassy-executor-macros/src/macros/task.rs @@ -2,47 +2,68 @@ use darling::export::NestedMeta; use darling::FromMeta; use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote}; -use syn::{parse_quote, Expr, ExprLit, ItemFn, Lit, LitInt, ReturnType, Type}; +use syn::visit::Visit; +use syn::{Expr, ExprLit, Lit, LitInt, ReturnType, Type}; -use crate::util::ctxt::Ctxt; +use crate::util::*; -#[derive(Debug, FromMeta)] +#[derive(Debug, FromMeta, Default)] struct Args { #[darling(default)] pool_size: Option, } -pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result { - let args = Args::from_list(args).map_err(|e| e.write_errors())?; +pub fn run(args: TokenStream, item: TokenStream) -> TokenStream { + let mut errors = TokenStream::new(); + + // If any of the steps for this macro fail, we still want to expand to an item that is as close + // to the expected output as possible. This helps out IDEs such that completions and other + // related features keep working. + let f: ItemFn = match syn::parse2(item.clone()) { + Ok(x) => x, + Err(e) => return token_stream_with_error(item, e), + }; + + let args = match NestedMeta::parse_meta_list(args) { + Ok(x) => x, + Err(e) => return token_stream_with_error(item, e), + }; + + let args = match Args::from_list(&args) { + Ok(x) => x, + Err(e) => { + errors.extend(e.write_errors()); + Args::default() + } + }; let pool_size = args.pool_size.unwrap_or(Expr::Lit(ExprLit { attrs: vec![], lit: Lit::Int(LitInt::new("1", Span::call_site())), })); - let ctxt = Ctxt::new(); - if f.sig.asyncness.is_none() { - ctxt.error_spanned_by(&f.sig, "task functions must be async"); + error(&mut errors, &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"); + error(&mut errors, &f.sig, "task functions must not be generic"); } if !f.sig.generics.where_clause.is_none() { - ctxt.error_spanned_by(&f.sig, "task functions must not have `where` clauses"); + error(&mut errors, &f.sig, "task functions must not have `where` clauses"); } if !f.sig.abi.is_none() { - ctxt.error_spanned_by(&f.sig, "task functions must not have an ABI qualifier"); + error(&mut errors, &f.sig, "task functions must not have an ABI qualifier"); } if !f.sig.variadic.is_none() { - ctxt.error_spanned_by(&f.sig, "task functions must not be variadic"); + error(&mut errors, &f.sig, "task functions must not be variadic"); } match &f.sig.output { ReturnType::Default => {} ReturnType::Type(_, ty) => match &**ty { Type::Tuple(tuple) if tuple.elems.is_empty() => {} Type::Never(_) => {} - _ => ctxt.error_spanned_by( + _ => error( + &mut errors, &f.sig, "task functions must either not return a value, return `()` or return `!`", ), @@ -55,26 +76,31 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result { - ctxt.error_spanned_by(arg, "task functions must not have receiver arguments"); + error(&mut errors, arg, "task functions must not have receiver arguments"); } - syn::FnArg::Typed(t) => match t.pat.as_mut() { - syn::Pat::Ident(id) => { - id.mutability = None; - args.push((id.clone(), t.attrs.clone())); + syn::FnArg::Typed(t) => { + check_arg_ty(&mut errors, &t.ty); + match t.pat.as_mut() { + syn::Pat::Ident(id) => { + id.mutability = None; + args.push((id.clone(), t.attrs.clone())); + } + _ => { + error( + &mut errors, + arg, + "pattern matching in task arguments is not yet supported", + ); + } } - _ => { - ctxt.error_spanned_by(arg, "pattern matching in task arguments is not yet supported"); - } - }, + } } } - ctxt.check()?; - let task_ident = f.sig.ident.clone(); let task_inner_ident = format_ident!("__{}_task", task_ident); - let mut task_inner = f; + let mut task_inner = f.clone(); let visibility = task_inner.vis.clone(); task_inner.vis = syn::Visibility::Inherited; task_inner.sig.ident = task_inner_ident.clone(); @@ -91,35 +117,43 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result ::embassy_executor::SpawnToken { - trait _EmbassyInternalTaskTrait { - type Fut: ::core::future::Future + 'static; - fn construct(#fargs) -> Self::Fut; - } + let mut task_outer_body = quote! { + trait _EmbassyInternalTaskTrait { + type Fut: ::core::future::Future + 'static; + fn construct(#fargs) -> Self::Fut; + } - impl _EmbassyInternalTaskTrait for () { - type Fut = impl core::future::Future + 'static; - fn construct(#fargs) -> Self::Fut { - #task_inner_ident(#(#full_args,)*) - } + impl _EmbassyInternalTaskTrait for () { + type Fut = impl core::future::Future + 'static; + fn construct(#fargs) -> Self::Fut { + #task_inner_ident(#(#full_args,)*) } - - const POOL_SIZE: usize = #pool_size; - static POOL: ::embassy_executor::raw::TaskPool<<() as _EmbassyInternalTaskTrait>::Fut, POOL_SIZE> = ::embassy_executor::raw::TaskPool::new(); - unsafe { POOL._spawn_async_fn(move || <() as _EmbassyInternalTaskTrait>::construct(#(#full_args,)*)) } } + + const POOL_SIZE: usize = #pool_size; + static POOL: ::embassy_executor::raw::TaskPool<<() as _EmbassyInternalTaskTrait>::Fut, POOL_SIZE> = ::embassy_executor::raw::TaskPool::new(); + unsafe { POOL._spawn_async_fn(move || <() as _EmbassyInternalTaskTrait>::construct(#(#full_args,)*)) } }; #[cfg(not(feature = "nightly"))] - let mut task_outer: ItemFn = parse_quote! { - #visibility fn #task_ident(#fargs) -> ::embassy_executor::SpawnToken { - const POOL_SIZE: usize = #pool_size; - static POOL: ::embassy_executor::_export::TaskPoolRef = ::embassy_executor::_export::TaskPoolRef::new(); - unsafe { POOL.get::<_, POOL_SIZE>()._spawn_async_fn(move || #task_inner_ident(#(#full_args,)*)) } - } + let mut task_outer_body = quote! { + const POOL_SIZE: usize = #pool_size; + static POOL: ::embassy_executor::_export::TaskPoolRef = ::embassy_executor::_export::TaskPoolRef::new(); + unsafe { POOL.get::<_, POOL_SIZE>()._spawn_async_fn(move || #task_inner_ident(#(#full_args,)*)) } }; - task_outer.attrs.append(&mut task_inner.attrs.clone()); + let task_outer_attrs = task_inner.attrs.clone(); + + if !errors.is_empty() { + task_outer_body = quote! { + #![allow(unused_variables, unreachable_code)] + let _x: ::embassy_executor::SpawnToken<()> = ::core::todo!(); + _x + }; + } + + // Copy the generics + where clause to avoid more spurious errors. + let generics = &f.sig.generics; + let where_clause = &f.sig.generics.where_clause; let result = quote! { // This is the user's task function, renamed. @@ -129,8 +163,27 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result ::embassy_executor::SpawnToken #where_clause{ + #task_outer_body + } + + #errors }; - Ok(result) + result +} + +fn check_arg_ty(errors: &mut TokenStream, ty: &Type) { + struct Visitor<'a> { + errors: &'a mut TokenStream, + } + + impl<'a, 'ast> Visit<'ast> for Visitor<'a> { + fn visit_type_impl_trait(&mut self, i: &'ast syn::TypeImplTrait) { + error(self.errors, i, "`impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic."); + } + } + + Visit::visit_type(&mut Visitor { errors }, ty); } diff --git a/embassy-executor-macros/src/util.rs b/embassy-executor-macros/src/util.rs new file mode 100644 index 000000000..ebd032a62 --- /dev/null +++ b/embassy-executor-macros/src/util.rs @@ -0,0 +1,74 @@ +use std::fmt::Display; + +use proc_macro2::{TokenStream, TokenTree}; +use quote::{ToTokens, TokenStreamExt}; +use syn::parse::{Parse, ParseStream}; +use syn::{braced, bracketed, token, AttrStyle, Attribute, Signature, Token, Visibility}; + +pub fn token_stream_with_error(mut tokens: TokenStream, error: syn::Error) -> TokenStream { + tokens.extend(error.into_compile_error()); + tokens +} + +pub fn error(s: &mut TokenStream, obj: A, msg: T) { + s.extend(syn::Error::new_spanned(obj.into_token_stream(), msg).into_compile_error()) +} + +/// Function signature and body. +/// +/// Same as `syn`'s `ItemFn` except we keep the body as a TokenStream instead of +/// parsing it. This makes the macro not error if there's a syntax error in the body, +/// which helps IDE autocomplete work better. +#[derive(Debug, Clone)] +pub struct ItemFn { + pub attrs: Vec, + pub vis: Visibility, + pub sig: Signature, + pub brace_token: token::Brace, + pub body: TokenStream, +} + +impl Parse for ItemFn { + fn parse(input: ParseStream) -> syn::Result { + let mut attrs = input.call(Attribute::parse_outer)?; + let vis: Visibility = input.parse()?; + let sig: Signature = input.parse()?; + + let content; + let brace_token = braced!(content in input); + while content.peek(Token![#]) && content.peek2(Token![!]) { + let content2; + attrs.push(Attribute { + pound_token: content.parse()?, + style: AttrStyle::Inner(content.parse()?), + bracket_token: bracketed!(content2 in content), + meta: content2.parse()?, + }); + } + + let mut body = Vec::new(); + while !content.is_empty() { + body.push(content.parse::()?); + } + let body = body.into_iter().collect(); + + Ok(ItemFn { + attrs, + vis, + sig, + brace_token, + body, + }) + } +} + +impl ToTokens for ItemFn { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.append_all(self.attrs.iter().filter(|a| matches!(a.style, AttrStyle::Outer))); + self.vis.to_tokens(tokens); + self.sig.to_tokens(tokens); + self.brace_token.surround(tokens, |tokens| { + tokens.append_all(self.body.clone()); + }); + } +} diff --git a/embassy-executor-macros/src/util/ctxt.rs b/embassy-executor-macros/src/util/ctxt.rs deleted file mode 100644 index 9c78cda01..000000000 --- a/embassy-executor-macros/src/util/ctxt.rs +++ /dev/null @@ -1,72 +0,0 @@ -// nifty utility borrowed from serde :) -// https://github.com/serde-rs/serde/blob/master/serde_derive/src/internals/ctxt.rs - -use std::cell::RefCell; -use std::fmt::Display; -use std::thread; - -use proc_macro2::TokenStream; -use quote::{quote, ToTokens}; - -/// 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-executor-macros/src/util/mod.rs b/embassy-executor-macros/src/util/mod.rs deleted file mode 100644 index 28702809e..000000000 --- a/embassy-executor-macros/src/util/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod ctxt; -- cgit From f0de0493084759a5e7310c816919996b201f0bc4 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sun, 20 Oct 2024 23:45:10 +0200 Subject: executor-macros: improve error messages. --- embassy-executor-macros/src/macros/task.rs | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) (limited to 'embassy-executor-macros/src') diff --git a/embassy-executor-macros/src/macros/task.rs b/embassy-executor-macros/src/macros/task.rs index 0404dba64..2f2aeda76 100644 --- a/embassy-executor-macros/src/macros/task.rs +++ b/embassy-executor-macros/src/macros/task.rs @@ -2,7 +2,7 @@ use darling::export::NestedMeta; use darling::FromMeta; use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote}; -use syn::visit::Visit; +use syn::visit::{self, Visit}; use syn::{Expr, ExprLit, Lit, LitInt, ReturnType, Type}; use crate::util::*; @@ -76,7 +76,7 @@ pub fn run(args: TokenStream, item: TokenStream) -> TokenStream { for arg in fargs.iter_mut() { match arg { syn::FnArg::Receiver(_) => { - error(&mut errors, arg, "task functions must not have receiver arguments"); + error(&mut errors, arg, "task functions must not have `self` arguments"); } syn::FnArg::Typed(t) => { check_arg_ty(&mut errors, &t.ty); @@ -180,6 +180,28 @@ fn check_arg_ty(errors: &mut TokenStream, ty: &Type) { } impl<'a, 'ast> Visit<'ast> for Visitor<'a> { + fn visit_type_reference(&mut self, i: &'ast syn::TypeReference) { + // only check for elided lifetime here. If not elided, it's checked by `visit_lifetime`. + if i.lifetime.is_none() { + error( + self.errors, + i.and_token, + "Arguments for tasks must live forever. Try using the `'static` lifetime.", + ) + } + visit::visit_type_reference(self, i); + } + + fn visit_lifetime(&mut self, i: &'ast syn::Lifetime) { + if i.ident.to_string() != "static" { + error( + self.errors, + i, + "Arguments for tasks must live forever. Try using the `'static` lifetime.", + ) + } + } + fn visit_type_impl_trait(&mut self, i: &'ast syn::TypeImplTrait) { error(self.errors, i, "`impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic."); } -- cgit From 03adeeddc2abd1c02a659f428f114848cf83e5ac Mon Sep 17 00:00:00 2001 From: Kaspar Schleiser Date: Mon, 4 Nov 2024 23:02:30 +0100 Subject: executor: allow overriding `embassy_executor` path in `task` macro --- embassy-executor-macros/src/macros/task.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) (limited to 'embassy-executor-macros/src') diff --git a/embassy-executor-macros/src/macros/task.rs b/embassy-executor-macros/src/macros/task.rs index 2f2aeda76..e8134c6a9 100644 --- a/embassy-executor-macros/src/macros/task.rs +++ b/embassy-executor-macros/src/macros/task.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + use darling::export::NestedMeta; use darling::FromMeta; use proc_macro2::{Span, TokenStream}; @@ -11,6 +13,9 @@ use crate::util::*; struct Args { #[darling(default)] pool_size: Option, + /// Use this to override the `embassy_executor` crate path. Defaults to `::embassy_executor`. + #[darling(default)] + embassy_executor: Option, } pub fn run(args: TokenStream, item: TokenStream) -> TokenStream { @@ -42,6 +47,10 @@ pub fn run(args: TokenStream, item: TokenStream) -> TokenStream { lit: Lit::Int(LitInt::new("1", Span::call_site())), })); + let embassy_executor = args + .embassy_executor + .unwrap_or(Expr::Verbatim(TokenStream::from_str("::embassy_executor").unwrap())); + if f.sig.asyncness.is_none() { error(&mut errors, &f.sig, "task functions must be async"); } @@ -131,13 +140,13 @@ pub fn run(args: TokenStream, item: TokenStream) -> TokenStream { } const POOL_SIZE: usize = #pool_size; - static POOL: ::embassy_executor::raw::TaskPool<<() as _EmbassyInternalTaskTrait>::Fut, POOL_SIZE> = ::embassy_executor::raw::TaskPool::new(); + static POOL: #embassy_executor::raw::TaskPool<<() as _EmbassyInternalTaskTrait>::Fut, POOL_SIZE> = #embassy_executor::raw::TaskPool::new(); unsafe { POOL._spawn_async_fn(move || <() as _EmbassyInternalTaskTrait>::construct(#(#full_args,)*)) } }; #[cfg(not(feature = "nightly"))] let mut task_outer_body = quote! { const POOL_SIZE: usize = #pool_size; - static POOL: ::embassy_executor::_export::TaskPoolRef = ::embassy_executor::_export::TaskPoolRef::new(); + static POOL: #embassy_executor::_export::TaskPoolRef = #embassy_executor::_export::TaskPoolRef::new(); unsafe { POOL.get::<_, POOL_SIZE>()._spawn_async_fn(move || #task_inner_ident(#(#full_args,)*)) } }; @@ -146,7 +155,7 @@ pub fn run(args: TokenStream, item: TokenStream) -> TokenStream { if !errors.is_empty() { task_outer_body = quote! { #![allow(unused_variables, unreachable_code)] - let _x: ::embassy_executor::SpawnToken<()> = ::core::todo!(); + let _x: #embassy_executor::SpawnToken<()> = ::core::todo!(); _x }; } @@ -164,7 +173,7 @@ pub fn run(args: TokenStream, item: TokenStream) -> TokenStream { #task_inner #(#task_outer_attrs)* - #visibility fn #task_ident #generics (#fargs) -> ::embassy_executor::SpawnToken #where_clause{ + #visibility fn #task_ident #generics (#fargs) -> #embassy_executor::SpawnToken #where_clause{ #task_outer_body } -- cgit From 26c6ab7587124b697c821a91bbf289efc29db822 Mon Sep 17 00:00:00 2001 From: Victor LEFEBVRE Date: Wed, 5 Feb 2025 00:16:10 +0100 Subject: Update main.rs --- embassy-executor-macros/src/macros/main.rs | 1 + 1 file changed, 1 insertion(+) (limited to 'embassy-executor-macros/src') diff --git a/embassy-executor-macros/src/macros/main.rs b/embassy-executor-macros/src/macros/main.rs index 14b1d9de2..a774cf622 100644 --- a/embassy-executor-macros/src/macros/main.rs +++ b/embassy-executor-macros/src/macros/main.rs @@ -161,6 +161,7 @@ pub fn run(args: TokenStream, item: TokenStream, arch: &Arch) -> TokenStream { let result = quote! { #[::embassy_executor::task()] + #[allow(clippy::future_not_send)] async fn __embassy_main(#fargs) #out { #f_body } -- cgit From 5a37dafec1af10263a46689b94c8099c335b332d Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 10 Mar 2025 09:56:04 +0100 Subject: preserve user attributes --- embassy-executor-macros/src/macros/main.rs | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'embassy-executor-macros/src') diff --git a/embassy-executor-macros/src/macros/main.rs b/embassy-executor-macros/src/macros/main.rs index a774cf622..24f61f30b 100644 --- a/embassy-executor-macros/src/macros/main.rs +++ b/embassy-executor-macros/src/macros/main.rs @@ -155,6 +155,11 @@ pub fn run(args: TokenStream, item: TokenStream, arch: &Arch) -> TokenStream { ), }; + let mut main_attrs = TokenStream::new(); + for attr in f.attrs { + main_attrs.extend(quote!(#attr)); + } + if !errors.is_empty() { main_body = quote! {loop{}}; } @@ -167,6 +172,7 @@ pub fn run(args: TokenStream, item: TokenStream, arch: &Arch) -> TokenStream { } #entry + #main_attrs fn main() -> #main_ret { #main_body } -- cgit From 695a6da322aa2d75c8f702b2ed8b67f9ad12c3a0 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Fri, 28 Mar 2025 18:59:02 +0100 Subject: Statically allocate task pools on stable Rust. Thanks @0e4ef622 for the awesome idea of how to do it and the first implementation. Co-Authored-By: Matthew Tran <0e4ef622@gmail.com> --- embassy-executor-macros/src/macros/task.rs | 38 ++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) (limited to 'embassy-executor-macros/src') diff --git a/embassy-executor-macros/src/macros/task.rs b/embassy-executor-macros/src/macros/task.rs index e8134c6a9..8a2a7fdb9 100644 --- a/embassy-executor-macros/src/macros/task.rs +++ b/embassy-executor-macros/src/macros/task.rs @@ -145,9 +145,43 @@ pub fn run(args: TokenStream, item: TokenStream) -> TokenStream { }; #[cfg(not(feature = "nightly"))] let mut task_outer_body = quote! { + const fn __task_pool_size(_: F) -> usize + where + F: #embassy_executor::_export::TaskFn, + { + ::core::mem::size_of::< + #embassy_executor::raw::TaskPool + >() + } + const fn __task_pool_align(_: F) -> usize + where + F: #embassy_executor::_export::TaskFn, + { + ::core::mem::align_of::< + #embassy_executor::raw::TaskPool + >() + } + + const fn __task_pool_new(_: F) -> #embassy_executor::raw::TaskPool + where + F: #embassy_executor::_export::TaskFn, + { + #embassy_executor::raw::TaskPool::new() + } + + const fn __task_pool_get(_: F) -> &'static #embassy_executor::raw::TaskPool + where + F: #embassy_executor::_export::TaskFn + { + unsafe { &*POOL.get().cast() } + } + const POOL_SIZE: usize = #pool_size; - static POOL: #embassy_executor::_export::TaskPoolRef = #embassy_executor::_export::TaskPoolRef::new(); - unsafe { POOL.get::<_, POOL_SIZE>()._spawn_async_fn(move || #task_inner_ident(#(#full_args,)*)) } + static POOL: #embassy_executor::_export::TaskPoolHolder< + {__task_pool_size(#task_inner_ident)}, + {__task_pool_align(#task_inner_ident)}, + > = unsafe { ::core::mem::transmute(__task_pool_new(#task_inner_ident)) }; + unsafe { __task_pool_get(#task_inner_ident)._spawn_async_fn(move || #task_inner_ident(#(#full_args,)*)) } }; let task_outer_attrs = task_inner.attrs.clone(); -- cgit From 35b353ab948256f4ae959767a7652c24bd42cd57 Mon Sep 17 00:00:00 2001 From: Matthew Tran <0e4ef622@gmail.com> Date: Sat, 29 Mar 2025 02:06:49 -0500 Subject: Fix ugly compiler errors --- embassy-executor-macros/src/macros/task.rs | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) (limited to 'embassy-executor-macros/src') diff --git a/embassy-executor-macros/src/macros/task.rs b/embassy-executor-macros/src/macros/task.rs index 8a2a7fdb9..e5523c5cd 100644 --- a/embassy-executor-macros/src/macros/task.rs +++ b/embassy-executor-macros/src/macros/task.rs @@ -145,33 +145,39 @@ pub fn run(args: TokenStream, item: TokenStream) -> TokenStream { }; #[cfg(not(feature = "nightly"))] let mut task_outer_body = quote! { - const fn __task_pool_size(_: F) -> usize + // We use Fut instead of F::Fut because F::Fut causes the compiler to generate some ugly + // unrelated errors when the task has a compile error. + const fn __task_pool_size(_: F) -> usize where - F: #embassy_executor::_export::TaskFn, + F: #embassy_executor::_export::TaskFn, + Fut: ::core::future::Future + 'static, { ::core::mem::size_of::< - #embassy_executor::raw::TaskPool + #embassy_executor::raw::TaskPool >() } - const fn __task_pool_align(_: F) -> usize + const fn __task_pool_align(_: F) -> usize where - F: #embassy_executor::_export::TaskFn, + F: #embassy_executor::_export::TaskFn, + Fut: ::core::future::Future + 'static, { ::core::mem::align_of::< - #embassy_executor::raw::TaskPool + #embassy_executor::raw::TaskPool >() } - const fn __task_pool_new(_: F) -> #embassy_executor::raw::TaskPool + const fn __task_pool_new(_: F) -> #embassy_executor::raw::TaskPool where - F: #embassy_executor::_export::TaskFn, + F: #embassy_executor::_export::TaskFn, + Fut: ::core::future::Future + 'static, { #embassy_executor::raw::TaskPool::new() } - const fn __task_pool_get(_: F) -> &'static #embassy_executor::raw::TaskPool + const fn __task_pool_get(_: F) -> &'static #embassy_executor::raw::TaskPool where - F: #embassy_executor::_export::TaskFn + F: #embassy_executor::_export::TaskFn, + Fut: ::core::future::Future + 'static, { unsafe { &*POOL.get().cast() } } -- cgit From 034e9fc218f1a348f451f56a5b9f3941fc046b1a Mon Sep 17 00:00:00 2001 From: Matthew Tran <0e4ef622@gmail.com> Date: Sat, 29 Mar 2025 02:45:48 -0500 Subject: Move macro helper functions to embassy-executor --- embassy-executor-macros/src/macros/task.rs | 35 +++--------------------------- 1 file changed, 3 insertions(+), 32 deletions(-) (limited to 'embassy-executor-macros/src') diff --git a/embassy-executor-macros/src/macros/task.rs b/embassy-executor-macros/src/macros/task.rs index e5523c5cd..91d6beee8 100644 --- a/embassy-executor-macros/src/macros/task.rs +++ b/embassy-executor-macros/src/macros/task.rs @@ -145,35 +145,6 @@ pub fn run(args: TokenStream, item: TokenStream) -> TokenStream { }; #[cfg(not(feature = "nightly"))] let mut task_outer_body = quote! { - // We use Fut instead of F::Fut because F::Fut causes the compiler to generate some ugly - // unrelated errors when the task has a compile error. - const fn __task_pool_size(_: F) -> usize - where - F: #embassy_executor::_export::TaskFn, - Fut: ::core::future::Future + 'static, - { - ::core::mem::size_of::< - #embassy_executor::raw::TaskPool - >() - } - const fn __task_pool_align(_: F) -> usize - where - F: #embassy_executor::_export::TaskFn, - Fut: ::core::future::Future + 'static, - { - ::core::mem::align_of::< - #embassy_executor::raw::TaskPool - >() - } - - const fn __task_pool_new(_: F) -> #embassy_executor::raw::TaskPool - where - F: #embassy_executor::_export::TaskFn, - Fut: ::core::future::Future + 'static, - { - #embassy_executor::raw::TaskPool::new() - } - const fn __task_pool_get(_: F) -> &'static #embassy_executor::raw::TaskPool where F: #embassy_executor::_export::TaskFn, @@ -184,9 +155,9 @@ pub fn run(args: TokenStream, item: TokenStream) -> TokenStream { const POOL_SIZE: usize = #pool_size; static POOL: #embassy_executor::_export::TaskPoolHolder< - {__task_pool_size(#task_inner_ident)}, - {__task_pool_align(#task_inner_ident)}, - > = unsafe { ::core::mem::transmute(__task_pool_new(#task_inner_ident)) }; + {#embassy_executor::_export::task_pool_size::<_, _, _, POOL_SIZE>(#task_inner_ident)}, + {#embassy_executor::_export::task_pool_align::<_, _, _, POOL_SIZE>(#task_inner_ident)}, + > = unsafe { ::core::mem::transmute(#embassy_executor::_export::task_pool_new::<_, _, _, POOL_SIZE>(#task_inner_ident)) }; unsafe { __task_pool_get(#task_inner_ident)._spawn_async_fn(move || #task_inner_ident(#(#full_args,)*)) } }; -- cgit From ef8d168df6fa1d8020d7490e4469d196a35077ce Mon Sep 17 00:00:00 2001 From: outfoxxed Date: Fri, 4 Apr 2025 01:33:51 -0700 Subject: executor: add executor selection to #[embassy_executor::main] --- embassy-executor-macros/src/lib.rs | 24 +++++++++++++++ embassy-executor-macros/src/macros/main.rs | 48 ++++++++++++++++++++++++++---- 2 files changed, 67 insertions(+), 5 deletions(-) (limited to 'embassy-executor-macros/src') diff --git a/embassy-executor-macros/src/lib.rs b/embassy-executor-macros/src/lib.rs index 5f2182f10..8e737db6a 100644 --- a/embassy-executor-macros/src/lib.rs +++ b/embassy-executor-macros/src/lib.rs @@ -173,3 +173,27 @@ pub fn main_std(args: TokenStream, item: TokenStream) -> TokenStream { pub fn main_wasm(args: TokenStream, item: TokenStream) -> TokenStream { main::run(args.into(), item.into(), &main::ARCH_WASM).into() } + +/// Creates a new `executor` instance and declares an application entry point for an unspecified architecture, spawning the corresponding function body as an async task. +/// +/// The following restrictions apply: +/// +/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks. +/// * The function must be declared `async`. +/// * The function must not use generics. +/// * Only a single `main` task may be declared. +/// +/// A user-defined entry macro and executor type must be provided via the `entry` and `executor` arguments of the `main` macro. +/// +/// ## Examples +/// Spawning a task: +/// ``` rust +/// #[embassy_executor::main(entry = "your_hal::entry", executor = "your_hal::Executor")] +/// async fn main(_s: embassy_executor::Spawner) { +/// // Function body +/// } +/// ``` +#[proc_macro_attribute] +pub fn main_unspecified(args: TokenStream, item: TokenStream) -> TokenStream { + main::run(args.into(), item.into(), &main::ARCH_UNSPECIFIED).into() +} diff --git a/embassy-executor-macros/src/macros/main.rs b/embassy-executor-macros/src/macros/main.rs index 24f61f30b..95722b19b 100644 --- a/embassy-executor-macros/src/macros/main.rs +++ b/embassy-executor-macros/src/macros/main.rs @@ -16,42 +16,57 @@ enum Flavor { pub(crate) struct Arch { default_entry: Option<&'static str>, flavor: Flavor, + executor_required: bool, } pub static ARCH_AVR: Arch = Arch { default_entry: Some("avr_device::entry"), flavor: Flavor::Standard, + executor_required: false, }; pub static ARCH_RISCV: Arch = Arch { default_entry: Some("riscv_rt::entry"), flavor: Flavor::Standard, + executor_required: false, }; pub static ARCH_CORTEX_M: Arch = Arch { default_entry: Some("cortex_m_rt::entry"), flavor: Flavor::Standard, + executor_required: false, }; pub static ARCH_SPIN: Arch = Arch { default_entry: None, flavor: Flavor::Standard, + executor_required: false, }; pub static ARCH_STD: Arch = Arch { default_entry: None, flavor: Flavor::Standard, + executor_required: false, }; pub static ARCH_WASM: Arch = Arch { default_entry: Some("wasm_bindgen::prelude::wasm_bindgen(start)"), flavor: Flavor::Wasm, + executor_required: false, +}; + +pub static ARCH_UNSPECIFIED: Arch = Arch { + default_entry: None, + flavor: Flavor::Standard, + executor_required: true, }; #[derive(Debug, FromMeta, Default)] struct Args { #[darling(default)] entry: Option, + #[darling(default)] + executor: Option, } pub fn run(args: TokenStream, item: TokenStream, arch: &Arch) -> TokenStream { @@ -112,9 +127,10 @@ pub fn run(args: TokenStream, item: TokenStream, arch: &Arch) -> TokenStream { error(&mut errors, &f.sig, "main function must have 1 argument: the spawner."); } - let entry = match args.entry.as_deref().or(arch.default_entry) { - None => TokenStream::new(), - Some(x) => match TokenStream::from_str(x) { + let entry = match (args.entry.as_deref(), arch.default_entry.as_deref()) { + (None, None) => TokenStream::new(), + (Some(x), _) | (None, Some(x)) if x == "" => TokenStream::new(), + (Some(x), _) | (None, Some(x)) => match TokenStream::from_str(x) { Ok(x) => quote!(#[#x]), Err(e) => { error(&mut errors, &f.sig, e); @@ -123,6 +139,28 @@ pub fn run(args: TokenStream, item: TokenStream, arch: &Arch) -> TokenStream { }, }; + let executor = match (args.executor.as_deref(), arch.executor_required) { + (None, true) => { + error( + &mut errors, + &f.sig, + "\ +No architecture selected for embassy-executor. Make sure you've enabled one of the `arch-*` features in your Cargo.toml. + +Alternatively, if you would like to use a custom executor implementation, specify it with the `executor` argument. +For example: `#[embassy_executor::main(entry = ..., executor = \"some_crate::Executor\")]", + ); + "" + } + (Some(x), _) => x, + (None, _) => "::embassy_executor::Executor", + }; + + let executor = TokenStream::from_str(executor).unwrap_or_else(|e| { + error(&mut errors, &f.sig, e); + TokenStream::new() + }); + let f_body = f.body; let out = &f.sig.output; @@ -134,7 +172,7 @@ pub fn run(args: TokenStream, item: TokenStream, arch: &Arch) -> TokenStream { ::core::mem::transmute(t) } - let mut executor = ::embassy_executor::Executor::new(); + let mut executor = #executor::new(); let executor = unsafe { __make_static(&mut executor) }; executor.run(|spawner| { spawner.must_spawn(__embassy_main(spawner)); @@ -144,7 +182,7 @@ pub fn run(args: TokenStream, item: TokenStream, arch: &Arch) -> TokenStream { Flavor::Wasm => ( quote!(Result<(), wasm_bindgen::JsValue>), quote! { - let executor = ::std::boxed::Box::leak(::std::boxed::Box::new(::embassy_executor::Executor::new())); + let executor = ::std::boxed::Box::leak(::std::boxed::Box::new(#executor::new())); executor.start(|spawner| { spawner.must_spawn(__embassy_main(spawner)); -- cgit From 5a07ea5d851768223e2e41342e69d14c1afb2b2b Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 9 Mar 2025 20:55:11 +0100 Subject: Add support for Cortex-A/R --- embassy-executor-macros/src/lib.rs | 25 +++++++++++++++++++++++++ embassy-executor-macros/src/macros/main.rs | 6 ++++++ 2 files changed, 31 insertions(+) (limited to 'embassy-executor-macros/src') diff --git a/embassy-executor-macros/src/lib.rs b/embassy-executor-macros/src/lib.rs index 8e737db6a..962ce2e75 100644 --- a/embassy-executor-macros/src/lib.rs +++ b/embassy-executor-macros/src/lib.rs @@ -70,6 +70,31 @@ pub fn main_cortex_m(args: TokenStream, item: TokenStream) -> TokenStream { main::run(args.into(), item.into(), &main::ARCH_CORTEX_M).into() } +/// Creates a new `executor` instance and declares an application entry point for Cortex-A/R +/// spawning the corresponding function body as an async task. +/// +/// The following restrictions apply: +/// +/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it +/// can use to spawn additional tasks. +/// * The function must be declared `async`. +/// * The function must not use generics. +/// * Only a single `main` task may be declared. +/// +/// ## Examples +/// Spawning a task: +/// +/// ``` rust +/// #[embassy_executor::main] +/// async fn main(_s: embassy_executor::Spawner) { +/// // Function body +/// } +/// ``` +#[proc_macro_attribute] +pub fn main_cortex_ar(args: TokenStream, item: TokenStream) -> TokenStream { + main::run(args.into(), item.into(), &main::ARCH_CORTEX_AR).into() +} + /// Creates a new `executor` instance and declares an architecture agnostic application entry point spawning /// the corresponding function body as an async task. /// diff --git a/embassy-executor-macros/src/macros/main.rs b/embassy-executor-macros/src/macros/main.rs index 95722b19b..a0e7b3401 100644 --- a/embassy-executor-macros/src/macros/main.rs +++ b/embassy-executor-macros/src/macros/main.rs @@ -37,6 +37,12 @@ pub static ARCH_CORTEX_M: Arch = Arch { executor_required: false, }; +pub static ARCH_CORTEX_AR: Arch = Arch { + default_entry: None, + flavor: Flavor::Standard, + executor_required: false, +}; + pub static ARCH_SPIN: Arch = Arch { default_entry: None, flavor: Flavor::Standard, -- cgit