executor: rename macro crate to embassy-executor-macros, bump it.

This commit is contained in:
Dario Nieuwenhuis
2023-12-07 00:43:18 +01:00
parent ad2d9040d9
commit ac2aec4e7a
17 changed files with 28 additions and 34 deletions

View File

@ -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 = []

View File

@ -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
<http://www.apache.org/licenses/LICENSE-2.0>)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
at your option.

View File

@ -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<NestedMeta>,
}
impl Parse for Args {
fn parse(input: &ParseBuffer) -> syn::Result<Self> {
let meta = Punctuated::<NestedMeta, Token![,]>::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()
}

View File

@ -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<String>,
}
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<TokenStream, TokenStream> {
#[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>(t: &mut T) -> &'static mut T {
::core::mem::transmute(t)
}
#main
};
Ok(result)
}

View File

@ -0,0 +1,2 @@
pub mod main;
pub mod task;

View File

@ -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<syn::Expr>,
}
pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result<TokenStream, TokenStream> {
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<impl Sized> {
type Fut = impl ::core::future::Future + 'static;
const POOL_SIZE: usize = #pool_size;
static POOL: ::embassy_executor::raw::TaskPool<Fut, POOL_SIZE> = ::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<impl Sized> {
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)
}

View File

@ -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<Option<Vec<syn::Error>>>,
}
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<A: ToTokens, T: Display>(&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<syn::Error>) -> 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");
}
}
}

View File

@ -0,0 +1 @@
pub mod ctxt;