Skip to content

Commit a0ebdf0

Browse files
authored
embassy #[main] convenience for RISC-V / Xtensa (esp-rs#841)
1 parent 0aa0232 commit a0ebdf0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+1723
-2224
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2727
- Added sleep support for ESP32-C3 with timer and GPIO wakeups (#795)
2828
- Support for ULP-RISCV including Delay and GPIO (#840)
2929
- Add bare-bones SPI slave support, DMA only (#580)
30+
- Embassy `#[main]` convenience macro
3031

3132
### Changed
3233

esp-hal-common/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ ufmt = ["ufmt-write"]
115115
async = ["embedded-hal-async", "eh1", "embassy-sync", "embassy-futures", "embedded-io-async"]
116116

117117
# Embassy support
118-
embassy = ["embassy-time"]
118+
embassy = ["embassy-time","procmacros/embassy"]
119119

120120
embassy-executor-interrupt = ["embassy", "embassy-executor"]
121121
embassy-executor-thread = ["embassy", "embassy-executor"]

esp-hal-procmacros/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,4 @@ esp32s3 = ["dep:object"]
4040

4141
interrupt = []
4242
rtc_slow = []
43+
embassy = []
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
use darling::ast::NestedMeta;
2+
use syn::{
3+
parse::{Parse, ParseBuffer},
4+
punctuated::Punctuated,
5+
Token,
6+
};
7+
8+
pub(crate) struct Args {
9+
pub(crate) meta: Vec<NestedMeta>,
10+
}
11+
12+
impl Parse for Args {
13+
fn parse(input: &ParseBuffer) -> syn::Result<Self> {
14+
let meta = Punctuated::<NestedMeta, Token![,]>::parse_terminated(input)?;
15+
Ok(Args {
16+
meta: meta.into_iter().collect(),
17+
})
18+
}
19+
}
20+
21+
pub(crate) mod main {
22+
use std::{cell::RefCell, fmt::Display, thread};
23+
24+
use darling::{export::NestedMeta, FromMeta};
25+
use proc_macro2::{Ident, Span, TokenStream};
26+
use proc_macro_crate::FoundCrate;
27+
use quote::{quote, ToTokens};
28+
use syn::{ReturnType, Type};
29+
30+
#[derive(Debug, FromMeta)]
31+
struct Args {}
32+
33+
pub fn run(
34+
args: &[NestedMeta],
35+
f: syn::ItemFn,
36+
main: TokenStream,
37+
) -> Result<TokenStream, TokenStream> {
38+
#[allow(unused_variables)]
39+
let args = Args::from_list(args).map_err(|e| e.write_errors())?;
40+
41+
let fargs = f.sig.inputs.clone();
42+
43+
let ctxt = Ctxt::new();
44+
45+
if f.sig.asyncness.is_none() {
46+
ctxt.error_spanned_by(&f.sig, "main function must be async");
47+
}
48+
if !f.sig.generics.params.is_empty() {
49+
ctxt.error_spanned_by(&f.sig, "main function must not be generic");
50+
}
51+
if !f.sig.generics.where_clause.is_none() {
52+
ctxt.error_spanned_by(&f.sig, "main function must not have `where` clauses");
53+
}
54+
if !f.sig.abi.is_none() {
55+
ctxt.error_spanned_by(&f.sig, "main function must not have an ABI qualifier");
56+
}
57+
if !f.sig.variadic.is_none() {
58+
ctxt.error_spanned_by(&f.sig, "main function must not be variadic");
59+
}
60+
match &f.sig.output {
61+
ReturnType::Default => {}
62+
ReturnType::Type(_, ty) => match &**ty {
63+
Type::Tuple(tuple) if tuple.elems.is_empty() => {}
64+
Type::Never(_) => {}
65+
_ => ctxt.error_spanned_by(
66+
&f.sig,
67+
"main function must either not return a value, return `()` or return `!`",
68+
),
69+
},
70+
}
71+
72+
if fargs.len() != 1 {
73+
ctxt.error_spanned_by(&f.sig, "main function must have 1 argument: the spawner.");
74+
}
75+
76+
ctxt.check()?;
77+
78+
let f_body = f.block;
79+
let out = &f.sig.output;
80+
81+
let result = quote! {
82+
#[::embassy_executor::task()]
83+
async fn __embassy_main(#fargs) #out {
84+
#f_body
85+
}
86+
87+
unsafe fn __make_static<T>(t: &mut T) -> &'static mut T {
88+
::core::mem::transmute(t)
89+
}
90+
91+
#main
92+
};
93+
94+
Ok(result)
95+
}
96+
97+
/// A type to collect errors together and format them.
98+
///
99+
/// Dropping this object will cause a panic. It must be consumed using
100+
/// `check`.
101+
///
102+
/// References can be shared since this type uses run-time exclusive mut
103+
/// checking.
104+
#[derive(Default)]
105+
pub struct Ctxt {
106+
// The contents will be set to `None` during checking. This is so that checking can be
107+
// enforced.
108+
errors: RefCell<Option<Vec<syn::Error>>>,
109+
}
110+
111+
impl Ctxt {
112+
/// Create a new context object.
113+
///
114+
/// This object contains no errors, but will still trigger a panic if it
115+
/// is not `check`ed.
116+
pub fn new() -> Self {
117+
Ctxt {
118+
errors: RefCell::new(Some(Vec::new())),
119+
}
120+
}
121+
122+
/// Add an error to the context object with a tokenenizable object.
123+
///
124+
/// The object is used for spanning in error messages.
125+
pub fn error_spanned_by<A: ToTokens, T: Display>(&self, obj: A, msg: T) {
126+
self.errors
127+
.borrow_mut()
128+
.as_mut()
129+
.unwrap()
130+
// Curb monomorphization from generating too many identical methods.
131+
.push(syn::Error::new_spanned(obj.into_token_stream(), msg));
132+
}
133+
134+
/// Add one of Syn's parse errors.
135+
#[allow(unused)]
136+
pub fn syn_error(&self, err: syn::Error) {
137+
self.errors.borrow_mut().as_mut().unwrap().push(err);
138+
}
139+
140+
/// Consume this object, producing a formatted error string if there are
141+
/// errors.
142+
pub fn check(self) -> Result<(), TokenStream> {
143+
let errors = self.errors.borrow_mut().take().unwrap();
144+
match errors.len() {
145+
0 => Ok(()),
146+
_ => Err(to_compile_errors(errors)),
147+
}
148+
}
149+
}
150+
151+
fn to_compile_errors(errors: Vec<syn::Error>) -> proc_macro2::TokenStream {
152+
let compile_errors = errors.iter().map(syn::Error::to_compile_error);
153+
quote!(#(#compile_errors)*)
154+
}
155+
156+
impl Drop for Ctxt {
157+
fn drop(&mut self) {
158+
if !thread::panicking() && self.errors.borrow().is_some() {
159+
panic!("forgot to check for errors");
160+
}
161+
}
162+
}
163+
164+
pub fn main() -> TokenStream {
165+
let (hal_crate, hal_crate_name) = crate::get_hal_crate();
166+
167+
let executor = match hal_crate {
168+
Ok(FoundCrate::Itself) => {
169+
quote!( #hal_crate_name::embassy::executor::Executor )
170+
}
171+
Ok(FoundCrate::Name(ref name)) => {
172+
let ident = Ident::new(&name, Span::call_site().into());
173+
quote!( #ident::embassy::executor::Executor )
174+
}
175+
Err(_) => {
176+
quote!(crate::embassy::executor::Executor)
177+
}
178+
};
179+
180+
quote! {
181+
#[entry]
182+
fn main() -> ! {
183+
let mut executor = #executor::new();
184+
let executor = unsafe { __make_static(&mut executor) };
185+
executor.run(|spawner| {
186+
spawner.must_spawn(__embassy_main(spawner));
187+
})
188+
}
189+
}
190+
}
191+
}

0 commit comments

Comments
 (0)