From ac2aec4e7a8e8a4c17c611810d382892398c9eb7 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Thu, 7 Dec 2023 00:43:18 +0100 Subject: executor: rename macro crate to embassy-executor-macros, bump it. --- embassy-executor-macros/Cargo.toml | 24 ++++ embassy-executor-macros/README.md | 15 +++ embassy-executor-macros/src/lib.rs | 175 +++++++++++++++++++++++++++++ embassy-executor-macros/src/macros/main.rs | 138 +++++++++++++++++++++++ embassy-executor-macros/src/macros/mod.rs | 2 + embassy-executor-macros/src/macros/task.rs | 114 +++++++++++++++++++ embassy-executor-macros/src/util/ctxt.rs | 73 ++++++++++++ embassy-executor-macros/src/util/mod.rs | 1 + embassy-executor/Cargo.toml | 4 +- embassy-executor/src/arch/cortex_m.rs | 2 +- embassy-executor/src/arch/riscv32.rs | 2 +- embassy-executor/src/arch/std.rs | 2 +- embassy-executor/src/arch/wasm.rs | 2 +- embassy-executor/src/lib.rs | 2 +- embassy-executor/src/raw/mod.rs | 4 +- embassy-executor/src/spawner.rs | 4 +- embassy-macros/Cargo.toml | 24 ---- embassy-macros/README.md | 21 ---- embassy-macros/src/lib.rs | 175 ----------------------------- embassy-macros/src/macros/main.rs | 138 ----------------------- embassy-macros/src/macros/mod.rs | 2 - embassy-macros/src/macros/task.rs | 114 ------------------- embassy-macros/src/util/ctxt.rs | 73 ------------ embassy-macros/src/util/mod.rs | 1 - 24 files changed, 553 insertions(+), 559 deletions(-) create mode 100644 embassy-executor-macros/Cargo.toml create mode 100644 embassy-executor-macros/README.md create mode 100644 embassy-executor-macros/src/lib.rs create mode 100644 embassy-executor-macros/src/macros/main.rs create mode 100644 embassy-executor-macros/src/macros/mod.rs create mode 100644 embassy-executor-macros/src/macros/task.rs create mode 100644 embassy-executor-macros/src/util/ctxt.rs create mode 100644 embassy-executor-macros/src/util/mod.rs delete mode 100644 embassy-macros/Cargo.toml delete mode 100644 embassy-macros/README.md delete mode 100644 embassy-macros/src/lib.rs delete mode 100644 embassy-macros/src/macros/main.rs delete mode 100644 embassy-macros/src/macros/mod.rs delete mode 100644 embassy-macros/src/macros/task.rs delete mode 100644 embassy-macros/src/util/ctxt.rs delete mode 100644 embassy-macros/src/util/mod.rs diff --git a/embassy-executor-macros/Cargo.toml b/embassy-executor-macros/Cargo.toml new file mode 100644 index 000000000..6ea7ddb71 --- /dev/null +++ b/embassy-executor-macros/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "embassy-executor-macros" +version = "0.4.0" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "macros for creating the entry point and tasks for embassy-executor" +repository = "https://github.com/embassy-rs/embassy" +categories = [ + "embedded", + "no-std", + "asynchronous", +] + +[dependencies] +syn = { version = "2.0.15", features = ["full", "extra-traits"] } +quote = "1.0.9" +darling = "0.20.1" +proc-macro2 = "1.0.29" + +[lib] +proc-macro = true + +[features] +nightly = [] \ No newline at end of file diff --git a/embassy-executor-macros/README.md b/embassy-executor-macros/README.md new file mode 100644 index 000000000..a959c85d5 --- /dev/null +++ b/embassy-executor-macros/README.md @@ -0,0 +1,15 @@ +# embassy-executor-macros + +An [Embassy](https://embassy.dev) project. + +NOTE: Do not use this crate directly. The macros are re-exported by `embassy-executor`. + +## License + +This work is licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. diff --git a/embassy-executor-macros/src/lib.rs b/embassy-executor-macros/src/lib.rs new file mode 100644 index 000000000..c9d58746a --- /dev/null +++ b/embassy-executor-macros/src/lib.rs @@ -0,0 +1,175 @@ +#![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. +/// +/// +/// The following restrictions apply: +/// +/// * The function must be declared `async`. +/// * The function must not use generics. +/// * The optional `pool_size` attribute must be 1 or greater. +/// +/// +/// ## Examples +/// +/// Declaring a task taking no arguments: +/// +/// ``` rust +/// #[embassy_executor::task] +/// async fn mytask() { +/// // Function body +/// } +/// ``` +/// +/// Declaring a task with a given pool size: +/// +/// ``` rust +/// #[embassy_executor::task(pool_size = 4)] +/// async fn mytask() { +/// // Function body +/// } +/// ``` +#[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() +} + +/// Creates a new `executor` instance and declares an application entry point for Cortex-M 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_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() +} + +/// 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: +/// +/// * 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 can be optionally provided via the `entry` argument to override the default of `riscv_rt::entry`. +/// +/// ## Examples +/// Spawning a task: +/// +/// ``` rust +/// #[embassy_executor::main] +/// async fn main(_s: embassy_executor::Spawner) { +/// // Function body +/// } +/// ``` +/// +/// Spawning a task using a custom entry macro: +/// ``` rust +/// #[embassy_executor::main(entry = "esp_riscv_rt::entry")] +/// async fn main(_s: embassy_executor::Spawner) { +/// // Function body +/// } +/// ``` +#[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() +} + +/// Creates a new `executor` instance and declares an application entry point for STD 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_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() +} + +/// Creates a new `executor` instance and declares an application entry point for WASM 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_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() +} diff --git a/embassy-executor-macros/src/macros/main.rs b/embassy-executor-macros/src/macros/main.rs new file mode 100644 index 000000000..3c0d58567 --- /dev/null +++ b/embassy-executor-macros/src/macros/main.rs @@ -0,0 +1,138 @@ +use darling::export::NestedMeta; +use darling::FromMeta; +use proc_macro2::TokenStream; +use quote::quote; +use syn::{Expr, ReturnType, Type}; + +use crate::util::ctxt::Ctxt; + +#[derive(Debug, FromMeta)] +struct Args { + #[darling(default)] + entry: Option, +} + +pub fn riscv(args: &[NestedMeta]) -> TokenStream { + let maybe_entry = match Args::from_list(args) { + Ok(args) => args.entry, + Err(e) => return e.write_errors(), + }; + + 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(), + }; + + 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.spawn(__embassy_main(spawner)).unwrap(); + }); + + 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"); + } + if !f.sig.generics.params.is_empty() { + ctxt.error_spanned_by(&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"); + } + if !f.sig.abi.is_none() { + ctxt.error_spanned_by(&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"); + } + 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( + &f.sig, + "main function must either not return a value, return `()` or return `!`", + ), + }, + } + + if fargs.len() != 1 { + ctxt.error_spanned_by(&f.sig, "main function must have 1 argument: the spawner."); + } + + ctxt.check()?; + + let f_body = f.block; + let out = &f.sig.output; + + 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) + } + + #main + }; + + Ok(result) +} diff --git a/embassy-executor-macros/src/macros/mod.rs b/embassy-executor-macros/src/macros/mod.rs new file mode 100644 index 000000000..572094ca6 --- /dev/null +++ b/embassy-executor-macros/src/macros/mod.rs @@ -0,0 +1,2 @@ +pub mod main; +pub mod task; diff --git a/embassy-executor-macros/src/macros/task.rs b/embassy-executor-macros/src/macros/task.rs new file mode 100644 index 000000000..5161e1020 --- /dev/null +++ b/embassy-executor-macros/src/macros/task.rs @@ -0,0 +1,114 @@ +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 crate::util::ctxt::Ctxt; + +#[derive(Debug, FromMeta)] +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())?; + + 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"); + } + if !f.sig.generics.params.is_empty() { + ctxt.error_spanned_by(&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"); + } + if !f.sig.abi.is_none() { + ctxt.error_spanned_by(&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"); + } + 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( + &f.sig, + "task functions must either not return a value, return `()` or return `!`", + ), + }, + } + + let mut arg_names = Vec::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(id) => { + arg_names.push(id.ident.clone()); + id.mutability = None; + } + _ => { + 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 visibility = task_inner.vis.clone(); + task_inner.vis = syn::Visibility::Inherited; + task_inner.sig.ident = task_inner_ident.clone(); + + #[cfg(feature = "nightly")] + let mut task_outer: ItemFn = parse_quote! { + #visibility fn #task_ident(#fargs) -> ::embassy_executor::SpawnToken { + type Fut = impl ::core::future::Future + 'static; + const POOL_SIZE: usize = #pool_size; + static POOL: ::embassy_executor::raw::TaskPool = ::embassy_executor::raw::TaskPool::new(); + unsafe { POOL._spawn_async_fn(move || #task_inner_ident(#(#arg_names,)*)) } + } + }; + #[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(#(#arg_names,)*)) } + } + }; + + task_outer.attrs.append(&mut task_inner.attrs.clone()); + + let result = quote! { + // This is the user's task function, renamed. + // We put it outside the #task_ident fn below, because otherwise + // the items defined there (such as POOL) would be in scope + // in the user's code. + #[doc(hidden)] + #task_inner + + #task_outer + }; + + Ok(result) +} diff --git a/embassy-executor-macros/src/util/ctxt.rs b/embassy-executor-macros/src/util/ctxt.rs new file mode 100644 index 000000000..74c872c3c --- /dev/null +++ b/embassy-executor-macros/src/util/ctxt.rs @@ -0,0 +1,73 @@ +// 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}; +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-executor-macros/src/util/mod.rs b/embassy-executor-macros/src/util/mod.rs new file mode 100644 index 000000000..28702809e --- /dev/null +++ b/embassy-executor-macros/src/util/mod.rs @@ -0,0 +1 @@ +pub mod ctxt; diff --git a/embassy-executor/Cargo.toml b/embassy-executor/Cargo.toml index a79bd89f5..ec5aca46d 100644 --- a/embassy-executor/Cargo.toml +++ b/embassy-executor/Cargo.toml @@ -32,7 +32,7 @@ defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } rtos-trace = { version = "0.1.2", optional = true } -embassy-macros = { version = "0.2.1", path = "../embassy-macros" } +embassy-executor-macros = { version = "0.4.0", path = "../embassy-executor-macros" } embassy-time = { version = "0.2", path = "../embassy-time", optional = true} critical-section = "1.1" @@ -66,7 +66,7 @@ executor-thread = [] executor-interrupt = [] # Enable nightly-only features -nightly = ["embassy-macros/nightly"] +nightly = ["embassy-executor-macros/nightly"] turbowakers = [] diff --git a/embassy-executor/src/arch/cortex_m.rs b/embassy-executor/src/arch/cortex_m.rs index 4a6d58575..5c517e0a2 100644 --- a/embassy-executor/src/arch/cortex_m.rs +++ b/embassy-executor/src/arch/cortex_m.rs @@ -51,7 +51,7 @@ mod thread { use core::arch::asm; use core::marker::PhantomData; - pub use embassy_macros::main_cortex_m as main; + pub use embassy_executor_macros::main_cortex_m as main; use crate::{raw, Spawner}; diff --git a/embassy-executor/src/arch/riscv32.rs b/embassy-executor/src/arch/riscv32.rs index ca12c3403..c56f502d3 100644 --- a/embassy-executor/src/arch/riscv32.rs +++ b/embassy-executor/src/arch/riscv32.rs @@ -7,7 +7,7 @@ pub use thread::*; mod thread { use core::marker::PhantomData; - pub use embassy_macros::main_riscv as main; + pub use embassy_executor_macros::main_riscv as main; use portable_atomic::{AtomicBool, Ordering}; use crate::{raw, Spawner}; diff --git a/embassy-executor/src/arch/std.rs b/embassy-executor/src/arch/std.rs index 598bb0509..b02b15988 100644 --- a/embassy-executor/src/arch/std.rs +++ b/embassy-executor/src/arch/std.rs @@ -8,7 +8,7 @@ mod thread { use std::marker::PhantomData; use std::sync::{Condvar, Mutex}; - pub use embassy_macros::main_std as main; + pub use embassy_executor_macros::main_std as main; use crate::{raw, Spawner}; diff --git a/embassy-executor/src/arch/wasm.rs b/embassy-executor/src/arch/wasm.rs index 3faa92575..f9d0f935c 100644 --- a/embassy-executor/src/arch/wasm.rs +++ b/embassy-executor/src/arch/wasm.rs @@ -8,7 +8,7 @@ mod thread { use core::marker::PhantomData; - pub use embassy_macros::main_wasm as main; + pub use embassy_executor_macros::main_wasm as main; use js_sys::Promise; use wasm_bindgen::prelude::*; diff --git a/embassy-executor/src/lib.rs b/embassy-executor/src/lib.rs index 897696150..4c6900a6d 100644 --- a/embassy-executor/src/lib.rs +++ b/embassy-executor/src/lib.rs @@ -6,7 +6,7 @@ // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; -pub use embassy_macros::task; +pub use embassy_executor_macros::task; macro_rules! check_at_most_one { (@amo [$($feats:literal)*] [] [$($res:tt)*]) => { diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index e9137f8fa..b16a1c7c3 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs @@ -5,7 +5,7 @@ //! ## WARNING: here be dragons! //! //! Using this module requires respecting subtle safety contracts. If you can, prefer using the safe -//! [executor wrappers](crate::Executor) and the [`embassy_executor::task`](embassy_macros::task) macro, which are fully safe. +//! [executor wrappers](crate::Executor) and the [`embassy_executor::task`](embassy_executor_macros::task) macro, which are fully safe. #[cfg_attr(target_has_atomic = "ptr", path = "run_queue_atomics.rs")] #[cfg_attr(not(target_has_atomic = "ptr"), path = "run_queue_critical_section.rs")] @@ -97,7 +97,7 @@ impl TaskRef { /// A `TaskStorage` must live forever, it may not be deallocated even after the task has finished /// running. Hence the relevant methods require `&'static self`. It may be reused, however. /// -/// Internally, the [embassy_executor::task](embassy_macros::task) macro allocates an array of `TaskStorage`s +/// Internally, the [embassy_executor::task](embassy_executor_macros::task) macro allocates an array of `TaskStorage`s /// in a `static`. The most common reason to use the raw `Task` is to have control of where /// the memory for the task is allocated: on the stack, or on the heap with e.g. `Box::leak`, etc. diff --git a/embassy-executor/src/spawner.rs b/embassy-executor/src/spawner.rs index 5a3a0dee1..271606244 100644 --- a/embassy-executor/src/spawner.rs +++ b/embassy-executor/src/spawner.rs @@ -115,9 +115,9 @@ impl Spawner { } } - // Used by the `embassy_macros::main!` macro to throw an error when spawn + // Used by the `embassy_executor_macros::main!` macro to throw an error when spawn // fails. This is here to allow conditional use of `defmt::unwrap!` - // without introducing a `defmt` feature in the `embassy_macros` package, + // without introducing a `defmt` feature in the `embassy_executor_macros` package, // which would require use of `-Z namespaced-features`. /// Spawn a task into an executor, panicking on failure. /// diff --git a/embassy-macros/Cargo.toml b/embassy-macros/Cargo.toml deleted file mode 100644 index e13104cfd..000000000 --- a/embassy-macros/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "embassy-macros" -version = "0.2.1" -edition = "2021" -license = "MIT OR Apache-2.0" -description = "macros for creating the entry point and tasks for embassy-executor" -repository = "https://github.com/embassy-rs/embassy" -categories = [ - "embedded", - "no-std", - "asynchronous", -] - -[dependencies] -syn = { version = "2.0.15", features = ["full", "extra-traits"] } -quote = "1.0.9" -darling = "0.20.1" -proc-macro2 = "1.0.29" - -[lib] -proc-macro = true - -[features] -nightly = [] \ No newline at end of file diff --git a/embassy-macros/README.md b/embassy-macros/README.md deleted file mode 100644 index d1d6f4cc4..000000000 --- a/embassy-macros/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# embassy-macros - -An [Embassy](https://embassy.dev) project. - -Macros for creating the main entry point and tasks that can be spawned by `embassy-executor`. - -NOTE: The macros are re-exported by the `embassy-executor` crate which should be used instead of adding a direct dependency on the `embassy-macros` crate. - -## Minimum supported Rust version (MSRV) - -The `task` and `main` macros require the type alias impl trait (TAIT) nightly feature in order to compile. - -## License - -This work is licensed under either of - -- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or - ) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) - -at your option. diff --git a/embassy-macros/src/lib.rs b/embassy-macros/src/lib.rs deleted file mode 100644 index c9d58746a..000000000 --- a/embassy-macros/src/lib.rs +++ /dev/null @@ -1,175 +0,0 @@ -#![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. -/// -/// -/// The following restrictions apply: -/// -/// * The function must be declared `async`. -/// * The function must not use generics. -/// * The optional `pool_size` attribute must be 1 or greater. -/// -/// -/// ## Examples -/// -/// Declaring a task taking no arguments: -/// -/// ``` rust -/// #[embassy_executor::task] -/// async fn mytask() { -/// // Function body -/// } -/// ``` -/// -/// Declaring a task with a given pool size: -/// -/// ``` rust -/// #[embassy_executor::task(pool_size = 4)] -/// async fn mytask() { -/// // Function body -/// } -/// ``` -#[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() -} - -/// Creates a new `executor` instance and declares an application entry point for Cortex-M 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_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() -} - -/// 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: -/// -/// * 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 can be optionally provided via the `entry` argument to override the default of `riscv_rt::entry`. -/// -/// ## Examples -/// Spawning a task: -/// -/// ``` rust -/// #[embassy_executor::main] -/// async fn main(_s: embassy_executor::Spawner) { -/// // Function body -/// } -/// ``` -/// -/// Spawning a task using a custom entry macro: -/// ``` rust -/// #[embassy_executor::main(entry = "esp_riscv_rt::entry")] -/// async fn main(_s: embassy_executor::Spawner) { -/// // Function body -/// } -/// ``` -#[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() -} - -/// Creates a new `executor` instance and declares an application entry point for STD 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_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() -} - -/// Creates a new `executor` instance and declares an application entry point for WASM 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_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() -} diff --git a/embassy-macros/src/macros/main.rs b/embassy-macros/src/macros/main.rs deleted file mode 100644 index 3c0d58567..000000000 --- a/embassy-macros/src/macros/main.rs +++ /dev/null @@ -1,138 +0,0 @@ -use darling::export::NestedMeta; -use darling::FromMeta; -use proc_macro2::TokenStream; -use quote::quote; -use syn::{Expr, ReturnType, Type}; - -use crate::util::ctxt::Ctxt; - -#[derive(Debug, FromMeta)] -struct Args { - #[darling(default)] - entry: Option, -} - -pub fn riscv(args: &[NestedMeta]) -> TokenStream { - let maybe_entry = match Args::from_list(args) { - Ok(args) => args.entry, - Err(e) => return e.write_errors(), - }; - - 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(), - }; - - 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.spawn(__embassy_main(spawner)).unwrap(); - }); - - 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"); - } - if !f.sig.generics.params.is_empty() { - ctxt.error_spanned_by(&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"); - } - if !f.sig.abi.is_none() { - ctxt.error_spanned_by(&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"); - } - 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( - &f.sig, - "main function must either not return a value, return `()` or return `!`", - ), - }, - } - - if fargs.len() != 1 { - ctxt.error_spanned_by(&f.sig, "main function must have 1 argument: the spawner."); - } - - ctxt.check()?; - - let f_body = f.block; - let out = &f.sig.output; - - 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) - } - - #main - }; - - Ok(result) -} diff --git a/embassy-macros/src/macros/mod.rs b/embassy-macros/src/macros/mod.rs deleted file mode 100644 index 572094ca6..000000000 --- a/embassy-macros/src/macros/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod main; -pub mod task; diff --git a/embassy-macros/src/macros/task.rs b/embassy-macros/src/macros/task.rs deleted file mode 100644 index 5161e1020..000000000 --- a/embassy-macros/src/macros/task.rs +++ /dev/null @@ -1,114 +0,0 @@ -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 crate::util::ctxt::Ctxt; - -#[derive(Debug, FromMeta)] -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())?; - - 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"); - } - if !f.sig.generics.params.is_empty() { - ctxt.error_spanned_by(&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"); - } - if !f.sig.abi.is_none() { - ctxt.error_spanned_by(&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"); - } - 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( - &f.sig, - "task functions must either not return a value, return `()` or return `!`", - ), - }, - } - - let mut arg_names = Vec::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(id) => { - arg_names.push(id.ident.clone()); - id.mutability = None; - } - _ => { - 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 visibility = task_inner.vis.clone(); - task_inner.vis = syn::Visibility::Inherited; - task_inner.sig.ident = task_inner_ident.clone(); - - #[cfg(feature = "nightly")] - let mut task_outer: ItemFn = parse_quote! { - #visibility fn #task_ident(#fargs) -> ::embassy_executor::SpawnToken { - type Fut = impl ::core::future::Future + 'static; - const POOL_SIZE: usize = #pool_size; - static POOL: ::embassy_executor::raw::TaskPool = ::embassy_executor::raw::TaskPool::new(); - unsafe { POOL._spawn_async_fn(move || #task_inner_ident(#(#arg_names,)*)) } - } - }; - #[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(#(#arg_names,)*)) } - } - }; - - task_outer.attrs.append(&mut task_inner.attrs.clone()); - - let result = quote! { - // This is the user's task function, renamed. - // We put it outside the #task_ident fn below, because otherwise - // the items defined there (such as POOL) would be in scope - // in the user's code. - #[doc(hidden)] - #task_inner - - #task_outer - }; - - Ok(result) -} diff --git a/embassy-macros/src/util/ctxt.rs b/embassy-macros/src/util/ctxt.rs deleted file mode 100644 index 74c872c3c..000000000 --- a/embassy-macros/src/util/ctxt.rs +++ /dev/null @@ -1,73 +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}; -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 deleted file mode 100644 index 28702809e..000000000 --- a/embassy-macros/src/util/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod ctxt; -- cgit