aboutsummaryrefslogtreecommitdiff
path: root/embassy-executor-macros/src
diff options
context:
space:
mode:
authorDario Nieuwenhuis <[email protected]>2023-12-07 00:43:18 +0100
committerDario Nieuwenhuis <[email protected]>2023-12-07 00:48:30 +0100
commitac2aec4e7a8e8a4c17c611810d382892398c9eb7 (patch)
tree6ac62c9f6d41e53d0f7f6d9737b1c0c206e531b3 /embassy-executor-macros/src
parentad2d9040d9f36d2523a22752e98f24c661643cb7 (diff)
executor: rename macro crate to embassy-executor-macros, bump it.
Diffstat (limited to 'embassy-executor-macros/src')
-rw-r--r--embassy-executor-macros/src/lib.rs175
-rw-r--r--embassy-executor-macros/src/macros/main.rs138
-rw-r--r--embassy-executor-macros/src/macros/mod.rs2
-rw-r--r--embassy-executor-macros/src/macros/task.rs114
-rw-r--r--embassy-executor-macros/src/util/ctxt.rs73
-rw-r--r--embassy-executor-macros/src/util/mod.rs1
6 files changed, 503 insertions, 0 deletions
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 @@
1#![doc = include_str!("../README.md")]
2extern crate proc_macro;
3
4use darling::ast::NestedMeta;
5use proc_macro::TokenStream;
6
7mod macros;
8mod util;
9use macros::*;
10use syn::parse::{Parse, ParseBuffer};
11use syn::punctuated::Punctuated;
12use syn::Token;
13
14struct Args {
15 meta: Vec<NestedMeta>,
16}
17
18impl Parse for Args {
19 fn parse(input: &ParseBuffer) -> syn::Result<Self> {
20 let meta = Punctuated::<NestedMeta, Token![,]>::parse_terminated(input)?;
21 Ok(Args {
22 meta: meta.into_iter().collect(),
23 })
24 }
25}
26
27/// Declares an async task that can be run by `embassy-executor`. The optional `pool_size` parameter can be used to specify how
28/// many concurrent tasks can be spawned (default is 1) for the function.
29///
30///
31/// The following restrictions apply:
32///
33/// * The function must be declared `async`.
34/// * The function must not use generics.
35/// * The optional `pool_size` attribute must be 1 or greater.
36///
37///
38/// ## Examples
39///
40/// Declaring a task taking no arguments:
41///
42/// ``` rust
43/// #[embassy_executor::task]
44/// async fn mytask() {
45/// // Function body
46/// }
47/// ```
48///
49/// Declaring a task with a given pool size:
50///
51/// ``` rust
52/// #[embassy_executor::task(pool_size = 4)]
53/// async fn mytask() {
54/// // Function body
55/// }
56/// ```
57#[proc_macro_attribute]
58pub fn task(args: TokenStream, item: TokenStream) -> TokenStream {
59 let args = syn::parse_macro_input!(args as Args);
60 let f = syn::parse_macro_input!(item as syn::ItemFn);
61
62 task::run(&args.meta, f).unwrap_or_else(|x| x).into()
63}
64
65/// Creates a new `executor` instance and declares an application entry point for Cortex-M spawning the corresponding function body as an async task.
66///
67/// The following restrictions apply:
68///
69/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks.
70/// * The function must be declared `async`.
71/// * The function must not use generics.
72/// * Only a single `main` task may be declared.
73///
74/// ## Examples
75/// Spawning a task:
76///
77/// ``` rust
78/// #[embassy_executor::main]
79/// async fn main(_s: embassy_executor::Spawner) {
80/// // Function body
81/// }
82/// ```
83#[proc_macro_attribute]
84pub fn main_cortex_m(args: TokenStream, item: TokenStream) -> TokenStream {
85 let args = syn::parse_macro_input!(args as Args);
86 let f = syn::parse_macro_input!(item as syn::ItemFn);
87 main::run(&args.meta, f, main::cortex_m()).unwrap_or_else(|x| x).into()
88}
89
90/// Creates a new `executor` instance and declares an application entry point for RISC-V spawning the corresponding function body as an async task.
91///
92/// The following restrictions apply:
93///
94/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks.
95/// * The function must be declared `async`.
96/// * The function must not use generics.
97/// * Only a single `main` task may be declared.
98///
99/// A user-defined entry macro can be optionally provided via the `entry` argument to override the default of `riscv_rt::entry`.
100///
101/// ## Examples
102/// Spawning a task:
103///
104/// ``` rust
105/// #[embassy_executor::main]
106/// async fn main(_s: embassy_executor::Spawner) {
107/// // Function body
108/// }
109/// ```
110///
111/// Spawning a task using a custom entry macro:
112/// ``` rust
113/// #[embassy_executor::main(entry = "esp_riscv_rt::entry")]
114/// async fn main(_s: embassy_executor::Spawner) {
115/// // Function body
116/// }
117/// ```
118#[proc_macro_attribute]
119pub fn main_riscv(args: TokenStream, item: TokenStream) -> TokenStream {
120 let args = syn::parse_macro_input!(args as Args);
121 let f = syn::parse_macro_input!(item as syn::ItemFn);
122 main::run(&args.meta, f, main::riscv(&args.meta))
123 .unwrap_or_else(|x| x)
124 .into()
125}
126
127/// Creates a new `executor` instance and declares an application entry point for STD spawning the corresponding function body as an async task.
128///
129/// The following restrictions apply:
130///
131/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks.
132/// * The function must be declared `async`.
133/// * The function must not use generics.
134/// * Only a single `main` task may be declared.
135///
136/// ## Examples
137/// Spawning a task:
138///
139/// ``` rust
140/// #[embassy_executor::main]
141/// async fn main(_s: embassy_executor::Spawner) {
142/// // Function body
143/// }
144/// ```
145#[proc_macro_attribute]
146pub fn main_std(args: TokenStream, item: TokenStream) -> TokenStream {
147 let args = syn::parse_macro_input!(args as Args);
148 let f = syn::parse_macro_input!(item as syn::ItemFn);
149 main::run(&args.meta, f, main::std()).unwrap_or_else(|x| x).into()
150}
151
152/// Creates a new `executor` instance and declares an application entry point for WASM spawning the corresponding function body as an async task.
153///
154/// The following restrictions apply:
155///
156/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks.
157/// * The function must be declared `async`.
158/// * The function must not use generics.
159/// * Only a single `main` task may be declared.
160///
161/// ## Examples
162/// Spawning a task:
163///
164/// ``` rust
165/// #[embassy_executor::main]
166/// async fn main(_s: embassy_executor::Spawner) {
167/// // Function body
168/// }
169/// ```
170#[proc_macro_attribute]
171pub fn main_wasm(args: TokenStream, item: TokenStream) -> TokenStream {
172 let args = syn::parse_macro_input!(args as Args);
173 let f = syn::parse_macro_input!(item as syn::ItemFn);
174 main::run(&args.meta, f, main::wasm()).unwrap_or_else(|x| x).into()
175}
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 @@
1use darling::export::NestedMeta;
2use darling::FromMeta;
3use proc_macro2::TokenStream;
4use quote::quote;
5use syn::{Expr, ReturnType, Type};
6
7use crate::util::ctxt::Ctxt;
8
9#[derive(Debug, FromMeta)]
10struct Args {
11 #[darling(default)]
12 entry: Option<String>,
13}
14
15pub fn riscv(args: &[NestedMeta]) -> TokenStream {
16 let maybe_entry = match Args::from_list(args) {
17 Ok(args) => args.entry,
18 Err(e) => return e.write_errors(),
19 };
20
21 let entry = maybe_entry.unwrap_or("riscv_rt::entry".into());
22 let entry = match Expr::from_string(&entry) {
23 Ok(expr) => expr,
24 Err(e) => return e.write_errors(),
25 };
26
27 quote! {
28 #[#entry]
29 fn main() -> ! {
30 let mut executor = ::embassy_executor::Executor::new();
31 let executor = unsafe { __make_static(&mut executor) };
32 executor.run(|spawner| {
33 spawner.must_spawn(__embassy_main(spawner));
34 })
35 }
36 }
37}
38
39pub fn cortex_m() -> TokenStream {
40 quote! {
41 #[cortex_m_rt::entry]
42 fn main() -> ! {
43 let mut executor = ::embassy_executor::Executor::new();
44 let executor = unsafe { __make_static(&mut executor) };
45 executor.run(|spawner| {
46 spawner.must_spawn(__embassy_main(spawner));
47 })
48 }
49 }
50}
51
52pub fn wasm() -> TokenStream {
53 quote! {
54 #[wasm_bindgen::prelude::wasm_bindgen(start)]
55 pub fn main() -> Result<(), wasm_bindgen::JsValue> {
56 let executor = ::std::boxed::Box::leak(::std::boxed::Box::new(::embassy_executor::Executor::new()));
57
58 executor.start(|spawner| {
59 spawner.spawn(__embassy_main(spawner)).unwrap();
60 });
61
62 Ok(())
63 }
64 }
65}
66
67pub fn std() -> TokenStream {
68 quote! {
69 fn main() -> ! {
70 let mut executor = ::embassy_executor::Executor::new();
71 let executor = unsafe { __make_static(&mut executor) };
72
73 executor.run(|spawner| {
74 spawner.must_spawn(__embassy_main(spawner));
75 })
76 }
77 }
78}
79
80pub fn run(args: &[NestedMeta], f: syn::ItemFn, main: TokenStream) -> Result<TokenStream, TokenStream> {
81 #[allow(unused_variables)]
82 let args = Args::from_list(args).map_err(|e| e.write_errors())?;
83
84 let fargs = f.sig.inputs.clone();
85
86 let ctxt = Ctxt::new();
87
88 if f.sig.asyncness.is_none() {
89 ctxt.error_spanned_by(&f.sig, "main function must be async");
90 }
91 if !f.sig.generics.params.is_empty() {
92 ctxt.error_spanned_by(&f.sig, "main function must not be generic");
93 }
94 if !f.sig.generics.where_clause.is_none() {
95 ctxt.error_spanned_by(&f.sig, "main function must not have `where` clauses");
96 }
97 if !f.sig.abi.is_none() {
98 ctxt.error_spanned_by(&f.sig, "main function must not have an ABI qualifier");
99 }
100 if !f.sig.variadic.is_none() {
101 ctxt.error_spanned_by(&f.sig, "main function must not be variadic");
102 }
103 match &f.sig.output {
104 ReturnType::Default => {}
105 ReturnType::Type(_, ty) => match &**ty {
106 Type::Tuple(tuple) if tuple.elems.is_empty() => {}
107 Type::Never(_) => {}
108 _ => ctxt.error_spanned_by(
109 &f.sig,
110 "main function must either not return a value, return `()` or return `!`",
111 ),
112 },
113 }
114
115 if fargs.len() != 1 {
116 ctxt.error_spanned_by(&f.sig, "main function must have 1 argument: the spawner.");
117 }
118
119 ctxt.check()?;
120
121 let f_body = f.block;
122 let out = &f.sig.output;
123
124 let result = quote! {
125 #[::embassy_executor::task()]
126 async fn __embassy_main(#fargs) #out {
127 #f_body
128 }
129
130 unsafe fn __make_static<T>(t: &mut T) -> &'static mut T {
131 ::core::mem::transmute(t)
132 }
133
134 #main
135 };
136
137 Ok(result)
138}
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 @@
1pub mod main;
2pub 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 @@
1use darling::export::NestedMeta;
2use darling::FromMeta;
3use proc_macro2::{Span, TokenStream};
4use quote::{format_ident, quote};
5use syn::{parse_quote, Expr, ExprLit, ItemFn, Lit, LitInt, ReturnType, Type};
6
7use crate::util::ctxt::Ctxt;
8
9#[derive(Debug, FromMeta)]
10struct Args {
11 #[darling(default)]
12 pool_size: Option<syn::Expr>,
13}
14
15pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result<TokenStream, TokenStream> {
16 let args = Args::from_list(args).map_err(|e| e.write_errors())?;
17
18 let pool_size = args.pool_size.unwrap_or(Expr::Lit(ExprLit {
19 attrs: vec![],
20 lit: Lit::Int(LitInt::new("1", Span::call_site())),
21 }));
22
23 let ctxt = Ctxt::new();
24
25 if f.sig.asyncness.is_none() {
26 ctxt.error_spanned_by(&f.sig, "task functions must be async");
27 }
28 if !f.sig.generics.params.is_empty() {
29 ctxt.error_spanned_by(&f.sig, "task functions must not be generic");
30 }
31 if !f.sig.generics.where_clause.is_none() {
32 ctxt.error_spanned_by(&f.sig, "task functions must not have `where` clauses");
33 }
34 if !f.sig.abi.is_none() {
35 ctxt.error_spanned_by(&f.sig, "task functions must not have an ABI qualifier");
36 }
37 if !f.sig.variadic.is_none() {
38 ctxt.error_spanned_by(&f.sig, "task functions must not be variadic");
39 }
40 match &f.sig.output {
41 ReturnType::Default => {}
42 ReturnType::Type(_, ty) => match &**ty {
43 Type::Tuple(tuple) if tuple.elems.is_empty() => {}
44 Type::Never(_) => {}
45 _ => ctxt.error_spanned_by(
46 &f.sig,
47 "task functions must either not return a value, return `()` or return `!`",
48 ),
49 },
50 }
51
52 let mut arg_names = Vec::new();
53 let mut fargs = f.sig.inputs.clone();
54
55 for arg in fargs.iter_mut() {
56 match arg {
57 syn::FnArg::Receiver(_) => {
58 ctxt.error_spanned_by(arg, "task functions must not have receiver arguments");
59 }
60 syn::FnArg::Typed(t) => match t.pat.as_mut() {
61 syn::Pat::Ident(id) => {
62 arg_names.push(id.ident.clone());
63 id.mutability = None;
64 }
65 _ => {
66 ctxt.error_spanned_by(arg, "pattern matching in task arguments is not yet supported");
67 }
68 },
69 }
70 }
71
72 ctxt.check()?;
73
74 let task_ident = f.sig.ident.clone();
75 let task_inner_ident = format_ident!("__{}_task", task_ident);
76
77 let mut task_inner = f;
78 let visibility = task_inner.vis.clone();
79 task_inner.vis = syn::Visibility::Inherited;
80 task_inner.sig.ident = task_inner_ident.clone();
81
82 #[cfg(feature = "nightly")]
83 let mut task_outer: ItemFn = parse_quote! {
84 #visibility fn #task_ident(#fargs) -> ::embassy_executor::SpawnToken<impl Sized> {
85 type Fut = impl ::core::future::Future + 'static;
86 const POOL_SIZE: usize = #pool_size;
87 static POOL: ::embassy_executor::raw::TaskPool<Fut, POOL_SIZE> = ::embassy_executor::raw::TaskPool::new();
88 unsafe { POOL._spawn_async_fn(move || #task_inner_ident(#(#arg_names,)*)) }
89 }
90 };
91 #[cfg(not(feature = "nightly"))]
92 let mut task_outer: ItemFn = parse_quote! {
93 #visibility fn #task_ident(#fargs) -> ::embassy_executor::SpawnToken<impl Sized> {
94 const POOL_SIZE: usize = #pool_size;
95 static POOL: ::embassy_executor::_export::TaskPoolRef = ::embassy_executor::_export::TaskPoolRef::new();
96 unsafe { POOL.get::<_, POOL_SIZE>()._spawn_async_fn(move || #task_inner_ident(#(#arg_names,)*)) }
97 }
98 };
99
100 task_outer.attrs.append(&mut task_inner.attrs.clone());
101
102 let result = quote! {
103 // This is the user's task function, renamed.
104 // We put it outside the #task_ident fn below, because otherwise
105 // the items defined there (such as POOL) would be in scope
106 // in the user's code.
107 #[doc(hidden)]
108 #task_inner
109
110 #task_outer
111 };
112
113 Ok(result)
114}
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 @@
1// nifty utility borrowed from serde :)
2// https://github.com/serde-rs/serde/blob/master/serde_derive/src/internals/ctxt.rs
3
4use std::cell::RefCell;
5use std::fmt::Display;
6use std::thread;
7
8use proc_macro2::TokenStream;
9use quote::{quote, ToTokens};
10use syn;
11
12/// A type to collect errors together and format them.
13///
14/// Dropping this object will cause a panic. It must be consumed using `check`.
15///
16/// References can be shared since this type uses run-time exclusive mut checking.
17#[derive(Default)]
18pub struct Ctxt {
19 // The contents will be set to `None` during checking. This is so that checking can be
20 // enforced.
21 errors: RefCell<Option<Vec<syn::Error>>>,
22}
23
24impl Ctxt {
25 /// Create a new context object.
26 ///
27 /// This object contains no errors, but will still trigger a panic if it is not `check`ed.
28 pub fn new() -> Self {
29 Ctxt {
30 errors: RefCell::new(Some(Vec::new())),
31 }
32 }
33
34 /// Add an error to the context object with a tokenenizable object.
35 ///
36 /// The object is used for spanning in error messages.
37 pub fn error_spanned_by<A: ToTokens, T: Display>(&self, obj: A, msg: T) {
38 self.errors
39 .borrow_mut()
40 .as_mut()
41 .unwrap()
42 // Curb monomorphization from generating too many identical methods.
43 .push(syn::Error::new_spanned(obj.into_token_stream(), msg));
44 }
45
46 /// Add one of Syn's parse errors.
47 #[allow(unused)]
48 pub fn syn_error(&self, err: syn::Error) {
49 self.errors.borrow_mut().as_mut().unwrap().push(err);
50 }
51
52 /// Consume this object, producing a formatted error string if there are errors.
53 pub fn check(self) -> Result<(), TokenStream> {
54 let errors = self.errors.borrow_mut().take().unwrap();
55 match errors.len() {
56 0 => Ok(()),
57 _ => Err(to_compile_errors(errors)),
58 }
59 }
60}
61
62fn to_compile_errors(errors: Vec<syn::Error>) -> proc_macro2::TokenStream {
63 let compile_errors = errors.iter().map(syn::Error::to_compile_error);
64 quote!(#(#compile_errors)*)
65}
66
67impl Drop for Ctxt {
68 fn drop(&mut self) {
69 if !thread::panicking() && self.errors.borrow().is_some() {
70 panic!("forgot to check for errors");
71 }
72 }
73}
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;